How can I animate fill opacity of a context (or add a fade-in effect) in HTML canvas?
For example at following example the ctx.fillStyle fill opacity is set to 0 and how can I animate it to 1?
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.fillStyle = 'rgba(255, 165, 0, 0)';
ctx.rect(20, 20, 150, 100);
ctx.fill();
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
You can't "animate" like you would with CSS. With a canvas, you're drawing the primative, so you'll have to do the math and timing yourself.
Here is a simple linear progression from one value to another.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
const duration = 1000; // ms
const step = 10; // ms
let opacity = 0;
function draw() {
if (opacity == 1) return;
opacity += (step / duration);
ctx.clearRect(20, 20, 150, 100);
ctx.beginPath();
ctx.fillStyle = `rgba(255, 165, 0, ${opacity})`;
ctx.rect(20, 20, 150, 100);
ctx.fill();
setTimeout(draw, step);
}
draw();
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
Basically, you keep track of the current opacity, how long you want it to go, and how frequently you want it to trigger. Then, you increase your opacity by the percent of your step to your duration and redraw.
Also, since you are dealing with opacity, you have to remember to clear it each step as well, or it'll get dark really fast.
You could also use window.requestAnimationFrame, but then (if you want to control the speed), you'll need to track time instead of step:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
const duration = 1000; // ms
let lastTime = performance.now();
let opacity = 0;
function draw(now) {
if (opacity >= 1) return;
opacity += ((now - lastTime) / duration);
lastTime = now;
ctx.clearRect(20, 20, 150, 100);
ctx.beginPath();
ctx.fillStyle = `rgba(255, 165, 0, ${opacity})`;
ctx.rect(20, 20, 150, 100);
ctx.fill();
window.requestAnimationFrame(draw);
}
draw(lastTime);
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
Notice now instead of opacity += step / duration, we're using the number of milliseconds since our last update opacity += (now - lastTime) / duration.
If you want to do different transitions (like step-in-out), you would need to tweak the amount of opacity increase as a factor of time.
You can achieve this using window.requestAnimationFrame:
var
/* Save the canvas' context. */
ctx = document.getElementById("myCanvas").getContext("2d"),
/* The starting opacity. */
opacity = 0,
/* The duration of the animation in milliseconds. */
duration = 500,
/* Cache the starting time in milliseconds since page load. */
past = performance.now();
/* The animation function. */
function animate(present) {
/* Find the difference between the previous and current times. */
var step = present - past;
/* Set the present time to past. */
past = present;
/* Increment the opacity by a linear step. */
opacity += step / duration;
/* Create the shape. */
ctx.beginPath();
ctx.fillStyle = "rgba(255, 165, 0, " + opacity + ")";
ctx.clearRect(20, 20, 150, 100);
ctx.rect(20, 20, 150, 100);
ctx.fill();
/* Continue the animation until the opacity is 1. */
if (opacity < 1) window.requestAnimationFrame(animate);
}
/* Start the animation. */
window.requestAnimationFrame(animate);
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
Notes:
You can adjust the speed of the animation by changing the duration to what suits you best. The value is in milliseconds.
On each call of animate, we use clearRect to clear the canvas, to avoid creating one shape on top of another, so that the opacity is incremented as expected.
Different browsers handle requestAnimationFrame differently, so to achieve a consistent, cross-browser result you must use a polyfill that accounts for the differences between browsers. I have provided one below.
Another way to proceed with the animation would be through the use of setInterval, but I believe this is a thing of the past. See this article for more.
Polyfill:
(This polyfill is a modified version of this one, created by Paul Irish)
;(function (prefices, lastTime) {
/* Iterate over every browser-engine-specific prefix. */
for (var i = 0; i < prefices.length && !window.requestAnimationFrame; i++) {
/* Normalise requestAnimationFrame and cancelAnimationFrame. */
window.requestAnimationFrame = window[prefices[i] + "RequestAnimationFrame"];
window.cancelAnimationFrame =
window[prefices[i] + "CancelAnimationFrame"] ||
window[prefices[i] + "CancelRequestAnimationFrame"];
}
/* If requestAnimationFrame is not defined use a custom function. */
window.requestAnimationFrame = window.requestAnimationFrame
|| function (callback, element) {
var
/* Save the present time and the time between it and the last time. */
now = Date.now() || new Date().getTime(),
timeToCall = Math.max(0, 16 - (now - lastTime)),
/* Save the id of the timeout. */
id = window.setTimeout(function () {
/* Call the callback function passing the time passed & the element. */
callback(now + timeToCall, element);
}, timeToCall);
/* Update the last time with the present time plus the time in between. */
lastTime = now + timeToCall;
/* Return the id of the timeout. */
return id;
};
/* If cancelAnimationFrame is not defined set it to clear the timeout. */
window.cancelAnimationFrame = window.cancelAnimationFrame || function (id) {
clearTimeout(id);
}
})(["webkit", "moz", "ms", "o"], 0);
Using same data array every time canvas moving but not continuously?
How to move canvas line left to right with that data array ?
if data array is completed use same data to continuously
here is my code:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<canvas id="canvas" width="160" height="160" style="background-color: black;"></canvas>
<script type='text/javascript'>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle ="#dbbd7a";
ctx.fill();
var fps = 1000;
var n = 0;
drawWave();
var data = [
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
];
function drawWave() {
setTimeout(function () {
requestAnimationFrame(drawWave);
ctx.lineWidth="2";
ctx.strokeStyle='green';
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Drawing code goes here
n += 1.5;
if (n > 200) {
n = 0;
}
ctx.beginPath();
for (var x = 0; x < n; x++) {
ctx.lineTo(x, data[x]);
}
ctx.stroke();
}, 1000/fps);
}
</script>
</body>
</html>
There are some problems with your code:
The reason for your non-continuous delay at the end of your animation loop...
Your array has 140 elements but your code is trying to plot 200 elements. The delay is your code trying to plot (200-140) non-existent array elements.
If(n>=data.length) // not n>200 (there are only 140 data elements to process!)
Using setTimeout with a fps interval of 1000 is not achievable (60fps is a practical maximum).
Var fps=60; // not fps=1000 is too fast to be achieved
You are incrementing n by 1.5 each time. This will skip some of your data elements. If this is not your intention you should instead increment n by 1.
n+=1; // not n+=1.5 which will skip some of your data[] elements
You are clearing the canvas and completely redrawing the wave in each animation loop. This works, but instead, keep your previous canvas and add the additional line to plot your next [x,y]. Only clear after you have drawn all your data points.
ctx.beginPath();
ctx.moveTo(n-1,data[n-1]);
ctx.lineTo(n,data[n]);
ctx.stroke();
Here is a Fiddle: http://jsfiddle.net/m1erickson/vPXkm/
[Addition based on OP's new information]
After your further clarification, I might understand what you desire
I think you want to have a wave pattern originate new plots from the left part of the canvas and push the existing data to the right using animation. After all x,y are plotted from your data[] source, you want the plots to repeat at the beginning of data[].
So here is how you do that:
Resize the canvas to twice the width of your data[].
Plot your data across the canvas twice. (data[0-140] and then data[0-140] again).
Convert the canvas to an image.
Resize the canvas to the width of your data[].
Animate the image across the canvas with an increasing offsetX to give the illusion of movement.
When you run out of image, reset the offset.
This reset appears seamless since the image is repeated twice.
Here is new code and another Fiddle: http://jsfiddle.net/m1erickson/qpWrj/
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<canvas id="canvas" width="560" height="160" style="background-color: black;"></canvas>
<script type='text/javascript'>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var data = [
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,
148,149,149,150,150,150,143,82,82,82,82,82,82,82,148
];
var fps = 30;
var offsetX=-data.length*2;
var waveImage;
createWaveImage();
function createWaveImage(){
// make the canvas double data.length
// and fill it with a background color
canvas.width=data.length*2;
ctx.fillStyle ="#dbbd7a";
ctx.strokeStyle="green";
ctx.lineWidth=2;
ctx.fillRect(0,0,canvas.width,canvas.height);
// plot data[] twice
ctx.beginPath();
ctx.moveTo(0,data[0]);
for(var x=1; x<data.length*2; x++){
var n=(x<data.length)?x:x-data.length;
ctx.lineTo(x,data[n]);
}
ctx.stroke();
// convert the canvas to an image
waveImage=new Image();
waveImage.onload=function(){
// resize the canvas to data.length
canvas.width=data.length;
// refill the canvas background color
ctx.fillStyle ="#dbbd7a";
ctx.fillRect(0,0,canvas.width,canvas.height);
// animate this wave image across the canvas
drawWave();
}
waveImage.src=canvas.toDataURL();
}
// animate the wave image in an endless loop
function drawWave() {
setTimeout(function () {
requestAnimationFrame(drawWave);
// Draw the wave image with an increasing offset
// so it appears to be moving
ctx.drawImage(waveImage,offsetX++,0);
// if we've run out of image, reset the offsetX
if((offsetX)>0){
offsetX=-data.length;
}
}, 1000/fps);
}
</script>
</body>
</html>