I'm trying to get up to speed on the canvas tag and I'm getting stuck when it comes to basic animations. I've searched around and I can get most of the tutorials I've found working, but I've had some trouble translating that into what I'm trying to do.
I have a bar graph that I'm building for my portfolio, and I would like to have the ability to animate the bars on the graph. Basically when the page loads, the bars start from the bottom of the graph, and grow upwards to where they need to be. I have a version I built in jquery found here to give an idea of what I'm after: http://jokedesigns.com/portfoliov6/
Could someone point me in the right direction on how to achieve the animations I'm looking for? Even if its a simple function I should be able to rework it into what I need. Most of the tutorials I've found mainly deal with rotation, I did find one that was a linear animation, but I still wasn't able to quite rework that into what I need.
Here is the code I have so far for the graph:
<html>
<head>
<script type="application/javascript">
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function draw() {
var canvas = document.getElementById("canvas");
var canvasbg = document.getElementById("canvasbg");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var ctx2 = canvasbg.getContext("2d");
var img = new Image();
img.onload = function(){
ctx2.drawImage(img,0,0);
};
img.src = 'skills_bg.jpg';
ctx.fillStyle = "#0a4888";
ctx.fillRect (0, 82, 34, 50);
ctx.fillStyle = "#ed9323";
ctx.fillRect (60, 82, 34, 50);
ctx.fillStyle = "#87982d";
ctx.fillRect (120, 82, 34, 50);
ctx.fillStyle = "#902e63";
ctx.fillRect (180, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (360, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (420, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (480, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (540, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (600, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (660, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (720, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (780, 82, 34, 50);
ctx.fillStyle = "#262627";
ctx.fillRect (840, 82, 34, 50);
var sources = {
ps: 'icn_ps.png',
ai: 'icn_ai.png',
dw: 'icn_dw.png',
id: 'icn_id.png',
ht: 'icn_html.png',
cs: 'icn_css.png',
js: 'icn_js.png',
sql: 'icn_mysql.png',
php: 'icn_php.png',
perl: 'icn_perl.png',
ruby: 'icn_ruby.png',
cplus: 'icn_cplusplus.png',
asp: 'icn_asp.png'
};
loadImages(sources, function(images) {
ctx.drawImage(images.ps, 0, 132, 36, 37);
ctx.drawImage(images.ai, 60, 132, 36, 37);
ctx.drawImage(images.dw, 120, 132, 36, 37);
ctx.drawImage(images.id, 180, 132, 36, 37);
ctx.drawImage(images.ht, 360, 132, 36, 37);
ctx.drawImage(images.cs, 420, 132, 36, 37);
ctx.drawImage(images.js, 480, 132, 36, 37);
ctx.drawImage(images.sql, 540, 132, 36, 37);
ctx.drawImage(images.php, 600, 132, 36, 37);
ctx.drawImage(images.perl, 660, 132, 36, 37);
ctx.drawImage(images.ruby, 720, 132, 36, 37);
ctx.drawImage(images.cplus, 780, 132, 36, 37);
ctx.drawImage(images.asp, 840, 132, 36, 37);
});
}
}
</script>
</head>
<body onload="draw();">
<canvas id="canvasbg" width="960" height="200" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="canvas" width="960" height="200" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</body>
</html>
You can use requestAnimationFrame to create animation loops to allow your bars to grow
To create uniform growth for bars that are uneven height you can apply a percentage
// this will grow all bars at an even rate
bar1Height = bar1.maxHeight * percentComplete/100;
bar2Height = bar2.maxHeight * percentComplete/100;
percentComplete++;
Here's example code and a Demo: http://jsfiddle.net/m1erickson/YR4D7/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var skillBars=[];
skillBars.push({max:200,color:"red"});
skillBars.push({max:75,color:"green"});
skillBars.push({max:275,color:"blue"});
var chartBottomY=325;
var chartBarWidth=30;
var chartBarPadding=20;
var percent=0;
animate();
function animate() {
// if not 100% done, request another frame
if(percent++<100){
requestAnimationFrame(animate);
}
// Drawing code goes here
ctx.clearRect(0,0,canvas.width,canvas.height);
var x=chartBarPadding;
for(var i=0;i<skillBars.length;i++){
var height=skillBars[i].max*percent/100;
ctx.fillStyle=skillBars[i].color;
ctx.fillRect(x,chartBottomY,chartBarWidth,-height);
x+=chartBarWidth+chartBarPadding;
}
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>
I see a couple issues.
First, you draw everything at one time, whereas you should draw after each image is loaded, but yiu will want to use setTimeout or something similar to allow the canvas element to be drawn.
You should also have a method for the bar, and have it remember the current step value and it will just draw the next block.
I would put onload on the image tag, image.onload function with return, and when it loaded draw the next bar and load the next one.
But remember to use setTimeout to allow the drawing to take place.
Here are some links that may help
JavaScript animation with multiple setTimeout
https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AnimatingtheCanvas/AnimatingtheCanvas.html
Related
My task is to create two canvases (big and small). In big one create 5 stars of different colors.
Task: by clicking on the star, change the color of the small canvas to the color of the star. Now the problem is that addEventListener only works on the last element. Any ideas?
.html template:
<canvas style="background-color:rgb(255, 255, 255); border: 1px solid black" id='big'>Обновите браузер</canvas>
<canvas style="background-color:rgb(255, 255, 255); border: 1px solid black" id='small'>Обновите браузер</canvas>
.js script:
const big = document.getElementById("big");
const small = document.getElementById("small");
big.height = 600;
big.width = 600;
small.height = 600;
small.width = 50;
function createStar(moveToX, moveToY, lineToX1, lineToY1, lineToX2, lineToY2, lineToX3, lineToY3, lineToX4, lineToY4, color) {
ctx = big.getContext('2d');
ctx.beginPath();
ctx.moveTo(moveToX, moveToY);
ctx.lineTo(lineToX1, lineToY1);
ctx.lineTo(lineToX2, lineToY2);
ctx.lineTo(lineToX3, lineToY3);
ctx.lineTo(lineToX4, lineToY4);
ctx.closePath();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fill();
ctx.stroke();
};
const red = new createStar(20, 60, 100, 60, 35, 110, 60, 25, 85, 110, 'red');
const blue = new createStar(120, 60, 200, 60, 135, 110, 160, 25, 185, 110, 'blue');
const green = new createStar(120, 160, 200, 160, 135, 210, 160, 125, 185, 210, 'green');
const black = new createStar(220, 460, 400, 460, 235, 560, 300, 400, 385, 560, 'black');
const yellow = new createStar(220, 260, 300, 260, 235, 310, 260, 225, 285, 310, 'yellow');
big.addEventListener('click', function(e){
if(ctx.isPointInPath(e.offsetX, e.offsetY)) {
small.style.backgroundColor = 'red';
} else {
small.style.backgroundColor = 'white';
}
});
context.isPointInPath(x, y) will check if the point at coords x and y is inside the current sub-path that's being traced in the context.
Every time you call ctx.beginPath(), this sub-path is cleared.
To workaround that you would have to retrace the whole list of stars and check one by one if the point falls in there.
However, there is also a Path2D interface that you can use.
This interface allows to hold such sub-path as their own object, and can be used by the context instead of the internal context's path by e.g ctx.fill(path), ctx.stroke(path), ctx.clip(path), ctx.scrollPathIntoView(path), ctx.drawFocusIfNeeded(path, element), and you probably guessed it, ctx.isPointInPath(path, x, y), or ctx.isPointInStroke(path, x, y).
So you can rewrite your code so that each star is stored in its own Path2D instance, store all these instances in an Array and then traverse that Array to check if any of these was hit by the point:
const stars = [];
const big = document.getElementById("big");
const small = document.getElementById("small");
big.height = 600;
big.width = 500;
small.height = 600;
small.width = 50;
const ctx = big.getContext("2d");
function createStar(moveToX, moveToY, lineToX1, lineToY1, lineToX2, lineToY2, lineToX3, lineToY3, lineToX4, lineToY4, color) {
const path = new Path2D();
// store our path in an object also hodling the color
stars.push({path, color});
path.moveTo(moveToX, moveToY);
path.lineTo(lineToX1, lineToY1);
path.lineTo(lineToX2, lineToY2);
path.lineTo(lineToX3, lineToY3);
path.lineTo(lineToX4, lineToY4);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fill(path);
ctx.stroke(path);
};
const red = new createStar(20, 60, 100, 60, 35, 110, 60, 25, 85, 110, 'red');
const blue = new createStar(120, 60, 200, 60, 135, 110, 160, 25, 185, 110, 'blue');
const green = new createStar(120, 160, 200, 160, 135, 210, 160, 125, 185, 210, 'green');
const black = new createStar(220, 460, 400, 460, 235, 560, 300, 400, 385, 560, 'black');
const yellow = new createStar(220, 260, 300, 260, 235, 310, 260, 225, 285, 310, 'yellow');
big.addEventListener('click', function(e) {
// find the first star that got clicked
const touched = stars.find((star) =>
ctx.isPointInPath(star.path, e.offsetX, e.offsetY)
);
if (touched) {
small.style.backgroundColor = touched.color;
} else {
small.style.backgroundColor = 'white';
}
});
canvas {
background-color: rgb(255, 255, 255);
border: 1px solid black;
}
<canvas id='big'>Обновите браузер</canvas>
<canvas id='small'>Обновите браузер</canvas>
This question already has an answer here:
fillStyle not a function
(1 answer)
Closed 5 years ago.
I am trying to get the image to display but i am having problems with the context.fillStyle working. It should be a pacman but cannot get it to work. Any help with being able to fix this would be very appreciated.
<canvas> id="canvas" width="600" height="600">
<p>Sorry, your browser doesnt understand the canvas element</p>
</canvas>
<script>
var canvas;
var context;
canvas = document.getelementbyid('canvas');
context = canvas.getContent('2d');
function drawCharacter() {
context.beginPath();
context.moveTo(118, 118);
context.lineTo(227, 73);
context.bezierCurveTo(209, 30, 167, 0, 118, 0);
context.bezierCurveTo(53, 0, 0.5, 53, 0, 118);
context.bezierCurveTo(0, 183, 53, 235, 118, 235);
context.bezierCurveTo(159, 235, 195, 215, 216, 183);
context.lineTo(118, 118);
context.closePath();
context.fillStyle() = "rgb(249, 243, 161)";
context.fill();
context.stroke();
context.beginPath();
context.arc(118, 60, 10, 0, 2*Math.PI, false);
context.fillStyle() = "rgb(0, 0, 0)";
context.fill();
}
drawcharacter();
</script>
CanvasRenderingContext2D.fillStyle is a property, not a method. Remove the parens:
context.fillStyle = "rgb(249, 243, 161)";
When I resize an image that I pull from a specific location on a spritesheet using drawImage, it appears as if drawImage will sometimes pull outside the source x + source y / swidth + sheight boundaries.
The following jfiddle illustrates what is happening: https://jsfiddle.net/cxuxyLj2/
The pertinent code is as follows:
drawingSurface.drawImage(heroSprite, 0, 64, 32, 64, 20, 144, 32, 64);
drawingSurface.drawImage(heroSprite, 0, 64, 32, 64, 20+50, 144, 32*2, 64*2);
There are lines that show above the RESIZED sprite's head (probably pulling the shoe of the same character on the first row), but not the original sprite's head.
My question is - can it be confirmed that this is a legitimate drawImage error, or am I doing something wrong?
It's actually because of the anti-alias when rescaling your sprite.
Because you do draw in a loop, without ever clearing the context, the anti-alias artifacts becomes bigger and bigger :
var canvas = document.querySelector("canvas");
var drawingSurface = canvas.getContext("2d");
var heroSprite = new Image();
heroSprite.src = "http://gopus.xepher.net/game/gfx/herosprite7.png";
render();
function render () {
setTimeout(render, 1000);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 20, 25, 32, 64);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 20+50, 25, 32*2, 64*2);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 150.6666687774, 25.33333222227, 32,64);
}
<canvas width="400" height="400" style="border:1px solid #000"></canvas>
You could avoid it by setting the imageSmoothingEnabled flag to false but it wasn't supported in IE prior to 10 ...
var canvas = document.querySelector("canvas");
var drawingSurface = canvas.getContext("2d");
drawingSurface.mozImageSmoothingEnabled = false;
drawingSurface.webkitImageSmoothingEnabled = false;
drawingSurface.msImageSmoothingEnabled = false;
drawingSurface.imageSmoothingEnabled = false;
var heroSprite = new Image();
heroSprite.src = "http://gopus.xepher.net/game/gfx/herosprite7.png";
render();
function render() {
setTimeout(render, 16);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 20, 25, 32, 64);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 20 + 50, 25, 32 * 2, 64 * 2);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 150.6666687774, 25.33333222227, 32, 64);
}
<canvas width="400" height="400" style="border:1px solid #000"></canvas>
...or by clearing the canvas at each call, you may avoid the artifacts to grow, but there will still be natural ones...
var canvas = document.querySelector("canvas");
var drawingSurface = canvas.getContext("2d");
var heroSprite = new Image();
heroSprite.src = "http://gopus.xepher.net/game/gfx/herosprite7.png";
render();
function render() {
setTimeout(render, 16);
drawingSurface.clearRect(0,0,canvas.width, canvas.height);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 20, 25, 32, 64);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 20 + 50, 25, 32 * 2, 64 * 2);
drawingSurface.drawImage(heroSprite, 0, 32, 32, 64, 150.6666687774, 25.33333222227, 32, 64);
}
<canvas width="400" height="400" style="border:1px solid #000"></canvas>
... so a final solution would be to use a buffer canvas, to only draw your sprite at normal scale (less anti-aliasing possible), then redraw
this buffer canvas at the wanted scale, you will keep in-image's anti-aliasing artifacts, but won't eat the border's of the cropped area anymore :
var canvas = document.querySelector("canvas");
var drawingSurface = canvas.getContext("2d");
var heroSprite = new Image();
heroSprite.src = "http://gopus.xepher.net/game/gfx/herosprite7.png";
var spriteCanvas = document.createElement('canvas');
var spriteCtx = spriteCanvas.getContext('2d');
var croppedSprite = function(x, y, width, height){
spriteCanvas.width = width;
spriteCanvas.height = height;
spriteCtx.drawImage(heroSprite, x, y, width, height, 0,0, width, height);
return spriteCanvas;
}
render();
function render() {
setTimeout(render, 16);
drawingSurface.drawImage(croppedSprite(0, 64, 32, 64), 20, 25, 32, 64);
drawingSurface.drawImage(croppedSprite(0, 64, 32, 64), 20 + 50, 25, 32 * 2, 64 * 2);
drawingSurface.drawImage(croppedSprite(0, 64, 32, 64), 150.6666687774, 25.33333222227, 32, 64);
}
<canvas width="400" height="400" style="border:1px solid #000"></canvas>
Also, you should definitely add more space between your sprites.
I'm trying to change fillstyle on focus in and focus out event. When the event call multiple time it's overlapping. Here my code:
function FillColorToFields(id, isOnFocus) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var coordinates = $('#hidCoord_' + id).val().split(',');
if (isOnFocus) {
context.fillStyle = "rgba(0,255,0,0.1)";
context.fillRect(coordinates[0], coordinates[1], coordinates[2], coordinates[3]);
} else {
context.fillStyle = "rgba(255, 255, 255, 0.5)";
context.fillRect(coordinates[0], coordinates[1], coordinates[2], coordinates[3]);
}
}
Simply use context.clearRect(x, y, width, height). Its purpose is to erase any previously drawn content of the rectangle (check out the documentation). See this forked fiddle for an example.
I set the image as canvas background image instead of drawing it to canvas. It's working fine now.
http://jsfiddle.net/vm47p2sk/8/
<canvas id="myCanvas"></canvas>
<a id = "green" href="#">On focus</a>
<a id = "transparent" href="#">On focus out</a>
$('#myCanvas').css("background-image", "url(https://www.webkit.org/blog-files/acid3-100.png)");
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.strokeStyle = "#009900";
context.lineWidth = 3;
context.strokeRect (5, 5, 100, 100);
$('#green').click(function (){
context.clearRect (8, 8, 94, 94);
context.fillStyle = "rgba(0,255,0,0.1)";
context.fillRect (8, 8, 94, 94);
});
$('#transparent').click(function (){
context.clearRect (8, 8, 94, 94);
context.fillStyle = "rgba(255, 255, 255, 0.1)";
context.fillRect (8, 8, 94, 94);
});
I've run into this problem on a few occasions but I can't seem to find a solid answer or solution to the problem.
I created a site that uses two canvases layered on top of each other which display properly in Chrome, but in Firefox only one of the canvases will display. Here's my code:
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
var interval;
var width, height;
// ------------- SETUP ----------------------
window.onload = function() {
width = document.width;
height = document.height;
setupC1();
setupC2();
}
function setupC1(){
canvas1 = document.createElement("canvas");
canvas1.width = document.width;
canvas1.height = document.height;
canvas1.style.position = "absolute";
canvas1.style.top = "0px";
canvas1.style.left = "0px";
canvas1.style.zIndex = "-2";
document.body.appendChild(canvas1);
ctx = canvas1.getContext("2d");
interval = setInterval(draw, 50);
}
function setupC2(){
canvas2 = document.createElement("canvas");
canvas2.width = "399";
canvas2.height = "274";
canvas2.style.position = "absolute";
canvas2.style.top = (window.innerHeight/2)-100 +"px";
canvas2.style.left = (window.innerWidth/2)-200 +"px";
canvas2.style.zIndex = "-1";
document.body.appendChild(canvas2);
ctx2 = canvas2.getContext("2d");
interval = setInterval(gun, 50);
}
// ------------- DRAW ----------------------
function draw() {
var x = Math.round(Math.random()*window.innerWidth);
var y = Math.round(Math.random()*window.innerHeight);
function rndColor() {
return '#' + ('00000' + (Math.random() * 16777216 << 0).toString(16)).substr(-6);
}
//draw here
ctx.strokeStyle=rndColor();
ctx.beginPath();
ctx.moveTo(window.innerWidth/2, window.innerHeight/2);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
function gun(){
ctx2.fillStyle="#000000";
ctx2.strokeStyle="#000000";
ctx2.beginPath();
ctx2.moveTo(51, 28);
ctx2.lineTo(66, 28);
ctx2.lineTo(71, 19);
ctx2.lineTo(75, 19);
ctx2.lineTo(77, 28);
ctx2.lineTo(191, 28);
ctx2.lineTo(191, 30);
ctx2.lineTo(238, 30);
ctx2.lineTo(238, 28);
ctx2.lineTo(350, 28);
ctx2.lineTo(355, 23);
ctx2.lineTo(358, 28);
ctx2.lineTo(368, 28);
ctx2.lineTo(368, 36);
ctx2.lineTo(373, 36);
ctx2.lineTo(374, 59);
ctx2.lineTo(368, 59);
ctx2.lineTo(368, 69);
ctx2.lineTo(371, 69);
ctx2.lineTo(371, 77);
ctx2.lineTo(368, 80);
ctx2.lineTo(368, 95);
ctx2.lineTo(265, 102);
ctx2.lineTo(265, 153);
ctx2.lineTo(164, 153);
ctx2.lineTo(169, 141);
ctx2.lineTo(249, 141);
ctx2.lineTo(249, 103);
ctx2.lineTo(198, 106);
ctx2.bezierCurveTo(192, 106, 195, 135, 200, 137);
ctx2.lineTo(194, 139);
ctx2.bezierCurveTo(190, 136, 178, 108, 180, 106);
ctx2.lineTo(169, 143);
ctx2.lineTo(156, 156);
ctx2.lineTo(157, 176);
ctx2.lineTo(143, 190);
ctx2.lineTo(144, 208);
ctx2.lineTo(129, 222);
ctx2.lineTo(129, 242);
ctx2.lineTo(120, 242);
ctx2.lineTo(120, 255);
ctx2.lineTo(42, 246);
ctx2.lineTo(48, 233);
ctx2.lineTo(37, 231);
ctx2.lineTo(25, 220);
ctx2.lineTo(75, 113);
ctx2.bezierCurveTo(75, 90, 55, 85, 46, 83);
ctx2.closePath();
ctx2.fill();
ctx2.stroke();
}
</script>
</head>
<body>
</body>
</html>
I feel like I may have heard about potential firefox issues with using negative z-indexes, could that really be the problem?
There are no width or height properties on the document object in the specs, so document.width ends up as undefined, and so does document.height. So you're setting your canvas1 to have a width and height of 0, which is the number undefined converts to.
You may want to use one of the things listed at https://developer.mozilla.org/en-US/docs/DOM/document.width#Alternatives and https://developer.mozilla.org/en-US/docs/DOM/document.height#Alternatives