Mona Coder
Mona Coder

Reputation: 6316

How to animate fill opacity of a context in HTML5 canvas?

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>

Upvotes: 0

Views: 1719

Answers (2)

Angel Politis
Angel Politis

Reputation: 11313

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:

  1. You can adjust the speed of the animation by changing the duration to what suits you best. The value is in milliseconds.

  2. 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.

  3. 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.

  4. 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);

Upvotes: 1

samanime
samanime

Reputation: 26547

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.

Upvotes: 1

Related Questions