Canvas Animation

Canvas Animation
Nikhil
Published on 2021-07-05 09:39:30

Use requestAnimationFrame() NOT setInterval() for animation loops


requestAnimationFrame is similar to setInterval, it but has these important improvements:

  • The animation code is synchronized with display refreshes for efficiency The clear + redraw code is scheduled, but not immediately executed. The browser will execute the clear + redraw code only when the display is ready to refresh. This synchronization with the refresh cycle increases your animation performance by giving your code the most available time in which to complete.
  • Every loop is always completed before another loop is allowed to start. This prevents "tearing", where the user sees an incomplete version of the drawing. The eye particularly notices tearing and is distracted when tearing occurs. So preventing tearing makes your animation appear smoother and more consistent.
  • Animation automatically stops when the user switches to a different browser tab. This saves power on mobile devices because the device is not wasting power computing an animation that the user can't currently see.

Device displays will refresh about 60 times per second so requestAnimationFrame can continuously redraw at about 60 "frames" per second. The eye sees motion at 20-30 frames per second so requestAnimationFrame can easily create the illusion of motion.

Notice that requestAnimationFrame is recalled at the end of each animateCircle. This is because each 'requestAnimatonFrameonly requests a single execution of the animation function.

Example: simple `requestAnimationFrame:

<!doctype html>
<html>
<head>
    <style>
        body {
            background-color: white;
        }

        #canvas {
            border: 1px solid red;
        }
    </style>
    <script>
        window.onload = (function() {
            // canvas related variables
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            var cw = canvas.width;
            var ch = canvas.height;
            // start the animation
            requestAnimationFrame(animate);

            function animate(currentTime) {
                // draw a full randomly circle
                var x = Math.random() * canvas.width;
                var y = Math.random() * canvas.height;
                var radius = 10 + Math.random() * 15;
                ctx.beginPath();
                ctx.arc(x, y, radius, 0, Math.PI * 2);
                ctx.fillStyle = '#' + Math.floor(Math.random() * 16777215).toString(16);
                ctx.fill();
                // request another loop of animation
                requestAnimationFrame(animate);
            }
        }); // end $(function(){});
    </script>
</head>
<body>
    <canvas id="canvas" width=512 height=512></canvas>
  </body>
</html>

 

Animate an image across the Canvas


This example loads and animates and image across the Canvas

Important Hint! Make sure you give your image time to fully load by using image.onload.

Annotated Code:

<!doctype html>
<html>
<head>
    <style>
        body {
            background-color: white;
        }

        #canvas {
            border: 1px solid red;
        }
    </style>
    <script>
        window.onload = (function() {
            // canvas related variables
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            var cw = canvas.width;
            var ch = canvas.height;
            // animation related variables
            var minX = 20; // Keep the image animating
            var maxX = 250; // between minX & maxX
            var x = minX; // The current X-coordinate
            var speedX = 1; // The image will move at 1px per loop
            var direction = 1; // The image direction: 1==righward, -1==leftward
            var y = 20; // The Y-coordinate
            // Load a new image
            // IMPORTANT!!! You must give the image time to load by using img.onload!
            var img = new Image();
            img.onload = start;
            img.src = "https://dl.dropboxusercontent.com/u/139992952/stackoverflow/sun.png";

            function start() {
                // the image is fully loaded sostart animating
                requestAnimationFrame(animate);
            }

            function animate(time) { // clear the canvas
                ctx.clearRect(0, 0, cw, ch);
                // draw
                ctx.drawImage(img, x, y);
                // update
                x += speedX * direction;
                // keep "x" inside min & max
                if (x < minX) {
                    x = minX;
                    direction *= -1;
                }
                if (x > maxX) {
                    x = maxX;
                    direction *= -1;
                }
                // request another loop of animation
                requestAnimationFrame(animate);
            }
        }); // end $(function(){});
    </script>
</head>
<body>
    <canvas id="canvas" width=512 height=512></canvas>
</body>
</html>

 

Set frame rate using requestAnimationFrame


Using requestAnimationFrame may on some systems update at more frames per second than the 60fps. 60fps is the default rate if the rendering can keep up. Some systems will run at 120fps maybe more.

If you use the following method you should only use frame rates that are integer divisions of 60 so that (60 / FRAMES_PER_SECOND) % 1 === 0 is true or you will get inconsistent frame rates.

const FRAMES_PER_SECOND = 30; // Valid values are 60,30,20,15,10...
// set the mim time to render the next frame
const FRAME_MIN_TIME = (1000 / 60) * (60 / FRAMES_PER_SECOND) - (1000 / 60) * 0.5;
var lastFrameTime = 0; // the last frame time
function update(time) {
    if (time - lastFrameTime < FRAME_MIN_TIME) { //skip the frame if the call is too early
        requestAnimationFrame(update);
        return; // return as there is nothing to do
    }
    lastFrameTime = time; // remember the time of the rendered frame
    // render the frame
    requestAnimationFrame(update); // get next farme
}
requestAnimationFrame(update); // start animation

 

Easing using Robert Penners equations


An easing causes some variable to change unevenly over a duration.

"variable" must be able to be expressed as a number, and can represent a remarkable variety of things:

  • an X-coordinate,
  • a rectangle's width,
  • an angle of rotation,
  • the red component of an R,G,B color.
  • anything that can be expressed as a number.

"duration" must be able to be expressed as a number and can also be a variety of things:

  • a period of time,
  • a distance to be travelled,
  • a quantity of animation loops to be executed,
  • anything that can be expressed as

"unevenly" means that the variable progresses from beginning to ending values unevenly:

  • faster at the beginning & slower at the ending -- or visa-versa,
  • overshoots the ending but backs up to the ending as the duration finishes,
  • repeatedly advances/retreats elastically during the duration,
  • "bounces" off the ending while coming to rest as the duration finishes.

// include the Easings object from above
var Easings = ...
    // Demo
    var startTime;
var beginningValue = 50; // beginning x-coordinate
var endingValue = 450; // ending x-coordinate
var totalChange = endingValue - beginningValue;
var totalDuration = 3000; // ms
var keys = Object.keys(Easings);
ctx.textBaseline = 'middle';
requestAnimationFrame(animate);

function animate(time) {
    var PI2 = Math.PI * 2;
    if (!startTime) {
        startTime = time;
    }
    var elapsedTime = Math.min(time - startTime, totalDuration);
    ctx.clearRect(0, 0, cw, ch);
    ctx.beginPath();
    for (var y = 0; y < keys.length; y++) {
        var key = keys[y];
        var easing = Easings[key];
        var easedX = easing(
            elapsedTime, beginningValue, totalChange, totalDuration);
        if (easedX > endingValue) {
            easedX = endingValue;
        }
        ctx.moveTo(easedX, y * 15);
        ctx.arc(easedX, y * 15 + 10, 5, 0, PI2);
        ctx.fillText(key, 460, y * 15 + 10 - 1);
    }
    ctx.fill();
    if (time < startTime + totalDuration) {
        requestAnimationFrame(animate);
    }
}

 

Animate at a specified interval (add a new rectangle every 1 second)


This example adds a new rectangle to the canvas every 1 second (== a 1 second interval)

<!doctype html>
<html>
<head>
    <style>
        body {
            background-color: white;
        }

        #canvas {
            border: 1px solid red;
        }
    </style>
    <script>
        window.onload = (function() {
            // canvas related variables
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            var cw = canvas.width;
            var ch = canvas.height;
            // animation interval variables
            var nextTime = 0; // the next animation begins at "nextTime"
            var duration = 1000; // run animation every 1000ms
            var x = 20; // the X where the next rect is drawn
            // start the animation
            requestAnimationFrame(animate);

            function animate(currentTime) {
                // wait for nextTime to occur
                if (currentTime < nextTime) {
                    // request another loop of animation
                    requestAnimationFrame(animate);
                    // time hasn't elapsed so just return
                    return;
                }
                // set nextTime
                nextTime = currentTime + duration;
                // add another rectangle every 1000ms
                ctx.fillStyle = '#' + Math.floor(Math.random() * 16777215).toString(16);
                ctx.fillRect(x, 30, 30, 30);
                // update X position for next rectangle
                x += 30;
                // request another loop of animation
                requestAnimationFrame(animate);
            }
        }); // end $(function(){});
    </script>
</head>
<body>
    <canvas id="canvas" width=512 height=512></canvas>
</body>
</html>

 

Animate at a specified time (an animated clock)


This example animates a clock showing the seconds as a filled wedge

Annotated Code:

<!doctype html>
<html>
<head>
    <style>
        body {
            background-color: white;
        }

        #canvas {
            border: 1px solid red;
        }
    </style>
    <script>
        window.onload = (function() {
            // canvas related variables
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            var cw = canvas.width;
            var ch = canvas.height;
            // canvas styling for the clock
            ctx.strokeStyle = 'lightgray';
            ctx.fillStyle = 'skyblue';
            ctx.lineWidth = 5;
            // cache often used values
            var PI = Math.PI;
            var fullCircle = PI * 2;
            var sa = -PI / 2; // == the 12 o'clock angle in context.arc
            // start the animation
            requestAnimationFrame(animate);

            function animate(currentTime) {
                // get the current seconds value from the system clock
                var date = new Date();
                var seconds = date.getSeconds();
                // clear the canvas
                ctx.clearRect(0, 0, cw, ch);
                // draw a full circle (== the clock face);
                ctx.beginPath();
                ctx.moveTo(100, 100);
                ctx.arc(100, 100, 75, 0, fullCircle);
                ctx.stroke();
                // draw a wedge representing the current seconds value
                ctx.beginPath();
                ctx.moveTo(100, 100);
                ctx.arc(100, 100, 75, sa, sa + fullCircle * seconds / 60);
                ctx.fill(); // request another loop of animation
                requestAnimationFrame(animate);
            }
        }); // end $(function(){});
    </script>
</head>
<body>
    <canvas id="canvas" width=512 height=512></canvas>
</body>
</html>

 

Don't draw animations in your event handlers (a simple sketch app)


During mousemove you get flooded with 30 mouse events per second. You might not be able to redraw your drawings at 30 times per second. Even if you can, you're probably wasting computing power by drawing when the browser is not ready to draw (wasted == across display refresh cycles). Therefore it makes sense to separate your users input events (like mousemove) from the drawing of your animations.

  • In event handlers, save all the event variables that control where drawings are positioned on the Canvas. But don't actually draw anything.
  • In a requestAnimationFrame loop, render all the drawings to the Canvas using the saved information. By not drawing in the event handlers, you are not forcing Canvas to try to refresh complex drawings at mouse event speeds.

By doing all drawing in requestAnimationFrame you gain all the benefits described in here Use 'requestanimationFrame' not 'setInterval' for animation loops.

Annotated Code::

<!doctype html>
<html>
<head>
    <style>
        body {
            background-color: ivory;
        }

        #canvas {
            border: 1px solid red;
        }
    </style>
    <script>
        window.onload = (function() {
            function log() {
                console.log.apply(console, arguments);
            }
            // canvas variables
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");
            var cw = canvas.width;
            var ch = canvas.height;
            // set canvas styling
            ctx.strokeStyle = 'skyblue';
            ctx.lineJoint = 'round';
            ctx.lineCap = 'round';
            ctx.lineWidth = 6; // handle windows scrolling & resizing
            function reOffset() {
                var BB = canvas.getBoundingClientRect();
                offsetX = BB.left;
                offsetY = BB.top;
            }
            var offsetX, offsetY;
            reOffset();
            window.onscroll = function(e) {
                reOffset();
            }
            window.onresize = function(e) {
                reOffset();
            }
            // vars to save points created during mousemove handling
            var points = [];
            var lastLength = 0;
            // start the animation loop
            requestAnimationFrame(draw);
            canvas.onmousemove = function(e) {
                handleMouseMove(e);
            }

            function handleMouseMove(e) {
                // tell the browser we're handling this event
                e.preventDefault();
                e.stopPropagation();
                // get the mouse position
                mouseX = parseInt(e.clientX - offsetX);
                mouseY = parseInt(e.clientY - offsetY);
                // save the mouse position in the points[] array
                // but don't draw anything
                points.push({
                    x: mouseX,
                    y: mouseY
                });
            }

            function draw() {
                // No additional points? Request another frame an return
                var length = points.length;
                if (length == lastLength) {
                    requestAnimationFrame(draw);
                    return;
                }
                // draw the additional points
                var point = points[lastLength];
                ctx.beginPath();
                ctx.moveTo(point.x, point.y)
                for (var i = lastLength; i < length; i++) {
                    point = points[i];
                    ctx.lineTo(point.x, point.y);
                }
                ctx.stroke();
                // request another animation loop
                requestAnimationFrame(draw);
            }
        }); // end window.onload
    </script>
</head>
<body>
    <h4>Move mouse over Canvas to sketch</h4>
    <canvas id="canvas" width=512 height=512></canvas>
</body>
</html>

 

Simple animation with 2D context and requestAnimationFrame


This example will show you how to create a simple animation using the canvas and the 2D context. It is assumed you know how to create and add a canvas to the DOM and obtain the context

// this example assumes ctx and canvas have been created
const textToDisplay = "This is an example that uses the canvas to animate some text.";
const textStyle = "white";
const BGStyle = "black"; // background style
const textSpeed = 0.2; // in pixels per millisecond
const textHorMargin = 8; // have the text a little outside the canvas
ctx.font = Math.floor(canvas.height * 0.8) + "px arial"; // size the font to 80% of canvas height
var textWidth = ctx.measureText(textToDisplay).width; // get the text width
var totalTextSize = (canvas.width + textHorMargin * 2 + textWidth);
ctx.textBaseline = "middle"; // not put the text in the vertical center
ctx.textAlign = "left"; // align to the left
var textX = canvas.width + 8; // start with the text off screen to the right
var textOffset = 0; // how far the text has moved
var startTime;
// this function is call once a frame which is approx 16.66 ms (60fps)
function update(time) { // time is passed by requestAnimationFrame
    if (startTime === undefined) { // get a reference for the start time if this is the first frame
        startTime = time;
    }
    ctx.fillStyle = BGStyle;
    ctx.fillRect(0, 0, canvas.width, canvas.height); // clear the canvas by
    drawing over it
    textOffset = ((time - startTime) * textSpeed) % (totalTextSize); // move the text left
    ctx.fillStyle = textStyle; // set the text style
    ctx.fillText(textToDisplay, textX - textOffset, canvas.height / 2); // render the text
    requestAnimationFrame(update); // all done request the next frame
}
requestAnimationFrame(update); // to start request the first frame

 

Animate from [x0,y0] to [x1,y1]


Use vectors to calculate incremental [x,y] from [startX,startY] to [endX,endY]

// dx is the total distance to move in the X direction
var dx = endX - startX;
// dy is the total distance to move in the Y direction
var dy = endY - startY;
// use a pct (percentage) to travel the total distances
// start at 0% which == the starting point
// end at 100% which == then ending point
var pct=0;
// use dx & dy to calculate where the current [x,y] is at a given pct
var x = startX + dx * pct/100;
var y = startY + dx * pct/100;

Example Code:

// canvas vars
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.style.border = '1px solid red';
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
// canvas styles
ctx.strokeStyle = 'skyblue';
ctx.fillStyle = 'blue';
// animating vars
var pct = 101;
var startX = 20;
var startY = 50;
var endX = 225;
var endY = 100;
var dx = endX - startX;
var dy = endY - startY;
// start animation loop running
requestAnimationFrame(animate);
// listen for mouse events
window.onmousedown = (function(e) {
    handleMouseDown(e);
});
window.onmouseup = (function(e) {
    handleMouseUp(e);
});
// constantly running loop
// will animate dot from startX,startY to endX,endY
function animate(time) {
    // demo: rerun animation
    if (++pct > 100) {
        pct = 0;
    }
    // update
    x = startX + dx * pct / 100;
    y = startY + dy * pct / 100;
    // draw
    ctx.clearRect(0, 0, cw, ch);
    ctx.beginPath();
    ctx.moveTo(startX, startY);
    ctx.lineTo(endX, endY);
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(x, y, 5, 0, Math.PI * 2);
    ctx.fill()
    // request another animation loop
    requestAnimationFrame(animate);
}

ATutorialHub Related Guide

Comments (8)

Leave a Comment

Your email address will not be published. Required fields are marked*

User Comments

html tutorial comments

panduranga gupta

2021-07-05 07:03:13

good website for learning and help me a lot

html tutorial comments

raju

2021-09-25 14:58:47

The awsome website i am looking like for a long time, good work atutorialhub team keep doing

html tutorial comments

Shivani

2021-09-01 15:03:56

Learning a lot from the courses present on atutorialhub. The courses are very well explained. Great experience

html tutorial comments

Harshitha

2021-09-10 15:05:45

It is very helpful to students and easy to learn the concepts

html tutorial comments

Sowmya

2021-09-14 15:06:41

Great job Tutorials are easy to understand Please make use of it

html tutorial comments

Zain Khan

2021-09-18 15:07:23

Great content and customized courses.

html tutorial comments

Rudrakshi Bhatt

2021-09-09 15:08:10

Well structured coursed and explained really well!

html tutorial comments

Pavana Somashekar

2021-09-11 15:09:08

Good platform for beginners and learn a lot on this website