Canvas Timers

Timers can be used to call a javascript function at set intervals. There are two ways to call a timer function:

setTimeout(functionName, milliseconds)
This will cause a function to be called once. The function will be called after waiting a specified number of milliseconds.
setInterval(functionName, milliseconds)
This will cause a function to be called repeatedly at specified time intervals.

A timer that has been set by setInterval() can be stopped by calling:

clearInterval(timerVariable)
This will cause timerVariable to stop calling its associated timer function.

In order to use clearInterval(), a timer variable needs to be associated with a call to setInterval. This is done below:

let myTimer = setInterval(myFunction, 100);

clearInterval(myTimer);

 

Canvas Animation

Use setTimeout() to set the start time of an animation. Use setInterval() to call a function that will control the actions of the animation.

The code below shows how to implement an animation interrupt.

let numberOfFrames = null;
let currentFrame = null;
function renderAnimation()
{
  currentFrame = 0;
  animationInterval = setInterval(renderCanvas, 1000);  // wait one second (1000 milli-seconds)
}


function renderCanvas() 
{
  // test to see if all of the frames have been played
  if(currentFrame === numberOfFrames)
{
clearInterval(animationInterval);
resetAnimation();
}
else // render the current frame and increment currentFrame { // render currentFrame ... // increment the currentFrame currentFrame++; }
}

The example below animates the drawing of a square as it moves across a canvas.

Example of an interval (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
#canvas
{
    border:1px solid black;
    width:500px;
    height:500px;
}

#loadingMessage
{
    position:absolute;
    top:100px;
    left:100px;
    z-index:100;
    font-size:50px;
}
</style>

<script>
const CANVAS_WIDTH = 200;
const CANVAS_HEIGHT = 200;

let canvas = null;
let ctx = null;
let animationInterval = null; // set to null when not running
const STEP_SIZE = 1;
const NUMBER_OF_FRAMES_PER_SECOND = 25;
const SPEED = 1000 / NUMBER_OF_FRAMES_PER_SECOND;
let x = null;


window.onload = onAllAssetsLoaded;
document.write("<div id='loadingMessage'>Loading...</div>");
function onAllAssetsLoaded()
{
    // hide the webpage loading message
    document.getElementById('loadingMessage').style.visibility = "hidden";

    canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    renderCanvas();

    startAnimationTimer();
}


function startAnimationTimer()
{
    if (animationInterval === null)
    {
        x = 0;
        animationInterval = setInterval(renderCanvas, SPEED);
    }
}


function renderCanvas()
{
    // clear any previous animation
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // test to see if all of the frames have been played
    if (x > (CANVAS_WIDTH + 1))
    {
        clearInterval(animationInterval);
        animationInterval = null; // set to null when not running
    }
    else // render the current frame and increment currentFrame
    {
        // render currentFrame
        ctx.fillRect(x, 100, 20, 20);

        // increment the currentFrame
        x += STEP_SIZE;
    }
}
</script>
</head>

<body>
<canvas id = "canvas" tabindex="1">
Your browser does not support the HTML5 'Canvas' tag.
</canvas>
</body>
</html> 

The example above uses a timer to control the display of a black square moving across the canvas.  When the square reaches the right side of the canvas, clearInterval() is called to remove the timer.

Adjust the code above, so that it has an image has a background, as shown here.

Adjust the above code, to show an image moving across another image, as shown here.

Separating The View from the Model

It is good coding practice to separate the rendering code from the state code. For the above example, this means that renderCanvas() should only deal with drawing.

Example where rendering and state code are separated (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
#canvas
{
    border:1px solid black;
    width:500px;
    height:500px;
}

#loadingMessage
{
    position:absolute;
    top:100px;
    left:100px;
    z-index:100;
    font-size:50px;
}
</style>

<script>
const CANVAS_WIDTH = 200;
const CANVAS_HEIGHT = 200;

let canvas = null;
let ctx = null;
let animationInterval = null; // set to null when not running
const STEP_SIZE = 1;
const NUMBER_OF_FRAMES_PER_SECOND = 25;
const SPEED = 1000 / NUMBER_OF_FRAMES_PER_SECOND;
let x = null;


window.onload = onAllAssetsLoaded;
document.write("<div id='loadingMessage'>Loading...</div>");
function onAllAssetsLoaded()
{
    // hide the webpage loading message
    document.getElementById('loadingMessage').style.visibility = "hidden";

    canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    renderCanvas();

    startAnimationTimer();
}


function renderCanvas()
{
    // only include drawing code here
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillRect(x, 100, 20, 20);
}


function updateRectangleState()
{
    // code dealing with state
    if (x > (CANVAS_WIDTH + 1))
    {
        clearInterval(animationInterval);
        animationInterval = null; // set to null when not running
    }
    else // render the current frame and increment currentFrame
    {
        // render currentFrame
        renderCanvas();

        // increment the currentFrame
        x += STEP_SIZE;
    }
}


function startAnimationTimer()
{
    if (animationInterval === null)
    {
        x = 0;
        animationInterval = setInterval(updateRectangleState, SPEED);
    }
}
</script>
</head>

<body>
<canvas id = "canvas" tabindex="1">
Your browser does not support the HTML5 'Canvas' tag.
</canvas>
</body>
</html>

Assigning A Sepearate timer to the Render Code

In the example above, the rendering is still tied to the rectangle's timer. This is okay if there is only one timer driven animation. However, it is common to have more than one animation connected to a timer. Therefore, it makes sense to connect the rendering to a separate timer. In the example below, several images are animated on the screen at the same time. It does not make sense to call the render code every time that any of these images animates. Instead, a separate timer is assigned to the render code, so that it renders independently of the animating square, triangle and circle animations. As each animation's code has been separated from the rendering code, each animation's code now only has to concern itself with the state of the animation.

Example of having a separate rendering code timer (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
#canvas
{
    border:1px solid black;
    width:500px;
    height:500px;
}

#loadingMessage
{
    position:absolute;
    top:100px;
    left:100px;
    z-index:100;
    font-size:50px;
}
</style>

<script>
const CANVAS_WIDTH = 200;
const CANVAS_HEIGHT = 200;

let canvas = null;
let ctx = null;
let backgroundImage = new Image();
backgroundImage.src = "images/dkit01.png";
let rectangleAnimationInterval = null; // set to null when not running
let circleAnimationInterval = null; // set to null when not running
let triangleAnimationInterval = null; // set to null when not running
const STEP_SIZE = 1;
const NUMBER_OF_FRAMES_PER_SECOND = 25;
const SPEED = 1000 / NUMBER_OF_FRAMES_PER_SECOND;
let rectangleX = 0;
let circleX = 0;
let triangleX = 0;

window.onload = onAllAssetsLoaded;
document.write("<div id='loadingMessage'>Loading...</div>");
function onAllAssetsLoaded()
{
    // hide the webpage loading message
    document.getElementById('loadingMessage').style.visibility = "hidden";

    canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    renderCanvas();

    startRectangleAnimationTimer();
    startCircleAnimationTimer();
    startTriangleAnimationTimer();

    setInterval(renderCanvas, SPEED);
}


function renderCanvas()
{
    // only include drawing code here
    ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);

    renderSquare();
    renderCircle();
    renderTriangle();
}


function  renderSquare()
{
    // Only contains rendering code
    ctx.beginPath();
    ctx.fillStyle = "black";
    ctx.fillRect(rectangleX, 30, 20, 20);
    ctx.closePath();
}


function  renderCircle()
{
    // Only contains rendering code
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(circleX, 70, 10, 0, Math.PI * 2);
    ctx.fill();
    ctx.closePath();
}


function  renderTriangle()
{
    // Only contains rendering code
    ctx.beginPath();
    ctx.fillStyle = "blue"; /* stroke colour */
    ctx.moveTo(triangleX + 10, 100);
    ctx.lineTo(triangleX + 20, 120);
    ctx.lineTo(triangleX, 120);
    ctx.lineTo(triangleX + 10, 100);
    ctx.lineTo(triangleX + 20, 120);
    ctx.fill();
    ctx.closePath();
}


function updateRectangleState()
{
    // test to see if all of the frames have been played 
    if (rectangleX > (CANVAS_WIDTH + 1))
    {
        clearInterval(rectangleAnimationInterval);
        rectangleAnimationInterval = null; // set to null when not running
    }
    else // render the current frame and increment currentFrame
    {
        // render currentFrame
        //renderCanvas(); // Not needed

        // increment the currentFrame
        rectangleX += STEP_SIZE;
    }
}



function updateCircleState()
{
    // test to see if all of the frames have been played 
    if (circleX > (CANVAS_WIDTH + 1))
    {
        clearInterval(circleAnimationInterval);
        circleAnimationInterval = null; // set to null when not running
    }
    else // render the current frame and increment currentFrame
    {
        circleX += STEP_SIZE;
    }
}


function updateTriangleState()
{
    // test to see if all of the frames have been played 
    if (triangleX > (CANVAS_WIDTH + 1))
    {
        clearInterval(triangleAnimationInterval);
        triangleAnimationInterval = null; // set to null when not running
    }
    else // render the current frame and increment currentFrame
    {
        triangleX += STEP_SIZE;
    }
}


function startRectangleAnimationTimer()
{
    if (rectangleAnimationInterval === null)
    {
        rectangleAnimationInterval = setInterval(updateRectangleState, 100); // fast
    }
}


function startCircleAnimationTimer()
{
    if (circleAnimationInterval === null)
    {
        circleAnimationInterval = setInterval(updateCircleState, 500); // medium
    }
}

function startTriangleAnimationTimer()
{
    if (triangleAnimationInterval === null)
    {
        trianageAnimationInterval = setInterval(updateTriangleState, 1200); // slow
    }
}

</script>
</head>

<body>
<canvas id = "canvas" tabindex="1">
Your browser does not support the HTML5 'Canvas' tag.
</canvas>
<p>Use the digits 1, 2 and 3 to start animation of square, circle and triangle. </p>
</body>
</html>

RequestAnimationFrame

Instead of using a timer, we can ask the system to take full control of when frames will be displayed. This will synch the code's diplay updating with the system screen refresh. This will result in smoother animations.

Separating the animation display from the event timers allows us to write cleaner code, as it allows us to place all of the drawing code separate to the state code. In the example below, the renderCanvas() function only deals with drawing items onto the canvas. The timer function updateRectanglePosition() is used to update the rectangle's state data.

Example of requestAnimationFrame() (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
#canvas
{
    border:1px solid black;
    width:500px;
    height:500px;
}

#loadingMessage
{
    position:absolute;
    top:100px;
    left:100px;
    z-index:100;
    font-size:50px;
}
</style>

<script>
const CANVAS_WIDTH = 200;
const CANVAS_HEIGHT = 200;

let canvas = null;
let ctx = null;
let animationInterval = null; // set to null when not running
const STEP_SIZE = 1;
const NUMBER_OF_FRAMES_PER_SECOND = 25;
const SPEED = 1000 / NUMBER_OF_FRAMES_PER_SECOND;
let x = null;


window.onload = onAllAssetsLoaded;
document.write("<div id='loadingMessage'>Loading...</div>");
function onAllAssetsLoaded()
{
    // hide the webpage loading message
    document.getElementById('loadingMessage').style.visibility = "hidden";

    canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    // setInterval(renderCanvas, SPEED);
    renderCanvas();  // Use this with requestAnimationFrame() instead of setInterval(renderCanvas, SPEED);

    startAnimationTimer();
}


function renderCanvas()
{
    // clear any previous animation
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // draw the rectangle in its current position
    ctx.fillRect(x, 100, 20, 20);

    /* Continuously call requestAnimationFrame() to keep rendering the canvas */
    requestAnimationFrame(renderCanvas); // recursively call next frame
}


function updateRectanglePosition()
{
// test to see if all of the frames have been played
    if (x > (CANVAS_WIDTH + 1))
    {
        clearInterval(animationInterval);
        animationInterval = null; // set to null when not running
    }
    else // render the current frame and increment currentFrame
    {
        // Note that we do not render the currentFrame here.
        // Instead, we use requestAnimationFrame()
        // ctx.clearRect(0, 0, canvas.width, canvas.height);

        // increment the currentFrame
        x += STEP_SIZE;
    }
}


function startAnimationTimer()
{
    if (animationInterval === null)
    {
        x = 0;
        animationInterval = setInterval(updateRectanglePosition, SPEED);
    }
}
</script>
</head>

<body>
<canvas id = "canvas" tabindex="1">
Your browser does not support the HTML5 'Canvas' tag.
</canvas>
</body>
</html>

Combining Animations

The code below shows a simple image and text animation.

The image and the text each has its own delay prior to running. For example, the function setTimeout(startImageAnimation, 2000) will cause the image animation to start running after two seconds (2000 millisconds).

Each animation should be given its own animation code. For example, updateImageAnimation() controls the behaviour of the image animation. This control includes the speed of the animation and the stop-condition of the animation.

Example code to animate an image and text (Run Example).

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>
            let canvas = null;
            let ctx = null;

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Start the animations */
                setTimeout(startImageAnimation, 0);
                setTimeout(startTextAnimation, 2700);

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation needs its own code */
            /******************************************************************************/
            /* These three are ALWAYS needed */
            let imageAnimationInterval = null;
            const IMAGE_FRAMERATE = 5; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
            let imageAnimationIsDisplayed = false;

            /* These variables depend on the animation */
            let image = new Image();
            image.src = "images/dkit04.png";
            let imageX = 0;
            let imageY = 0;
            let size = 0;

            function startImageAnimation()
            {
                imageAnimationIsDisplayed = true;
                imageAnimationInterval = setInterval(updateImageAnimation, IMAGE_FRAMERATE);
            }


            function stopImageAnimation()
            {
                imageAnimationIsDisplayed = true;
                clearInterval(imageAnimationInterval);
                imageAnimationInterval = null; // set to null when not running           
            }


            function stopAndHideImageAnimation()
            {
                stopImageAnimation();
                imageAnimationIsDisplayed = false;
            }


            function updateImageAnimation()
            {
                size++;

                if (size === canvas.width)
                {
                    stopImageAnimation();
                }
            }


            function renderImageAnimation()
            {
                if (imageAnimationIsDisplayed)
                {
                    ctx.drawImage(image, imageX, imageY, size, size);
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* These three are ALWAYS needed */
            let textAnimationInterval = null;
            const TEXT_FRAMERATE = 5; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
            let textAnimationIsDisplayed = false;

            let textX = 500; // canvas width
            let minTextX = 35;
            let textY = 300;
            let message = "DkIT";
            let fontSize = 200;
            let textColour = "red";

            /* Start the animation */
            function startTextAnimation()
            {
                textAnimationIsDisplayed = true;
                textAnimationInterval = setInterval(updateTextAnimation, TEXT_FRAMERATE);
            }


            function stopTextAnimation()
            {
                textAnimationIsDisplayed = true;
                clearInterval(textAnimationInterval);
                textAnimationInterval = null; // set to null when not running           
            }


            function stopAndHideTextAnimation()
            {
                stopAndHideTextAnimation();
                textAnimationIsDisplayed = false;
            }


            function updateTextAnimation()
            {
                textX--;
                if (textX === minTextX)
                {
                    stopTextAnimation();
                }
            }


            function renderTextAnimation()
            {
                if (textAnimationIsDisplayed)
                {
                    ctx.fillStyle = textColour;
                    ctx.font = fontSize + "px Times Roman";
                    ctx.fillText(message, textX, textY);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);


                /* Step 3 of 3 */
                /* Drawn the animations */
                renderImageAnimation();
                renderTextAnimation();
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Object Oriented Animations

In the above example, we can only use each function name once. Because each function name can only be used once, we need to come up with unique names, such as startImageAnimation() and startTextAnimation(). This becomes very messy when we have several animations. By using object oriented code, it is possible to contain all of the animation code for each animation inside a single object. Object oriented code will allow us to use the same function name for different objects, so that we can have a start() function inside both an ImageAnimation object and a TextAnimation. As shown in the example below, this results in much cleaner code.

The example below shows how to convert the above example into object oriented code.

Example of an object oriented animation (Run Example)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>                          
            let canvas = null;
            let ctx = null;

            let image = new Image();
            image.src = "images/dkit04.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new ImageAnimation(0);
                animation[1] = new TextAnimation(2700);

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation object needs its own code */
            /******************************************************************************/
            /* ImageAnimation object */
            class ImageAnimation
            {
                constructor(animationStartDelay, 5)

                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;

                    /* These variables depend on the animation */
                    this.x = 0;
                    this.y = 0;
                    this.size = 0;

                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                update()
                {
                    this.size++;

                    if (this.size >= canvas.width)
                    {
                        this.stop();
                    }
                }

                render()
                {
                    ctx.drawImage(image, this.x, this.y, this.size, this.size);
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* TextAnimation object */
            class TextAnimation
            {
                constructor(animationStartDelay, 5)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;

                    /* These variables depend on the animation */
                    this.x = canvas.width;
                    this.minX = 35;
                    this.y = 300;
                    this.message = "DkIT";
                    this.fontSize = 200;

                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                update()
                {
                    this.x--;
                    if (this.x === this.minX)
                    {
                        this.stop();
                    }
                }

                render()
                {
                    ctx.fillStyle = "red";
                    ctx.font = this.fontSize + "px Times Roman";
                    ctx.fillText(this.message, this.x, this.y);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Class Inheritance

The TextAnimation and ImageAnimation objects in the above code both use much of the same code. All of the functions, except for update() and render(), are exactly the same for the two objects. The code that is the same for both objects can be extracted to a higher-level object. In the example below, the higher-level class is called Animaiton. Importantly, no matter how many animations we have, they will all extend from the same Animation class. The code that remains in the individual animations will only have functions for update() and render(). As a result, it will be much easier to develop and maintain. In the example below, only the code highlighted in red depends on the specific animations that we are running. The code rest of the code is the same for every animation that we make. This provides us with a template that we can use for all of our animaitons.

Example using class inheritance (Run example)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>                         
            let canvas = null;
            let ctx = null;

            let image = new Image();
            image.src = "images/dkit04.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";
                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;
                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new ImageAnimation(0);
                animation[1] = new TextAnimation(2700);
                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation object needs its own code */
            /******************************************************************************/
            /* Animation */
            class Animation
            {
                constructor(animationStartDelay, frameRate)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;
                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                /* update() and render() will be different for each animation */
                update()
                {
                }

                render()
                {
                }
            }


            /* ImageAnimation object */
            class ImageAnimation extends Animation
            {
                constructor(animationStartDelay)
                {
                    super(animationStartDelay, 5);

                    /* These variables depend on the animation */
                    this.x = 0;
                    this.y = 0;
                    this.size = 0;
                }

                update()
                {
                    this.size++;
                    if (this.size >= canvas.width)
                    {
                        this.stop();
                    }
                }

                render()
                {
                    ctx.drawImage(image, this.x, this.y, this.size, this.size);
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* TextAnimation object */
            class TextAnimation extends Animation
            {
                constructor(animationStartDelay)
                {
                    super(animationStartDelay, 5);

                    /* These variables depend on the animation */
                    this.x = canvas.width;
                    this.minX = 35;
                    this.y = 300;
                    this.message = "DkIT";
                    this.fontSize = 200;
                    this.colour = "red";
                }

                update()
                {
                    this.x--;
                    if (this.x === this.minX)
                    {
                        this.stop();
                    }
                }

                render()
                {
                    ctx.fillStyle = this.colour;
                    ctx.font = this.fontSize + "px Times Roman";
                    ctx.fillText(this.message, this.x, this.y);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Separate Files

We can move the code for the various animations into seperate javascript files. This will make it much easier to maintain the code. In the example below, only the code highlighted in red needs to change for different animations.

Example of object animation code moved into seperate javascript files (Run Example)

index.html file

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Worked example from lecture notes</title> <style> #canvas { border:1px solid black; width:500px; height:500px; } #loadingMessage { position:absolute; top:100px; left:100px; z-index:100; font-size:50px; } </style> <!-- Move each object to its own javaScript file --> <script src="js/Animation.js"></script> <!-- This file will always be included --> <script src="js/ImageAnimation.js"></script> <script src="js/TextAnimation.js"></script> <script> const CANVAS_WIDTH = 500; const CANVAS_HEIGHT = 500; let canvas = null; let ctx = null; /* Declare any images, video or audio that will be used by any of the animations */ let image = new Image(); image.src = "images/dkit04.png"; /* Animation array */ let animation = []; window.onload = onAllAssetsLoaded; document.write("<div id='loadingMessage'>Loading...</div>"); function onAllAssetsLoaded() { // stopAndHide the webpage loading message document.getElementById('loadingMessage').style.visibility = "hidden"; canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); canvas.width = CANVAS_WIDTH; canvas.height = CANVAS_HEIGHT; /* Step 1 of 3 */ /* Each animation needs to be declared as an object */ animation[0] = new ImageAnimation(0); animation[1] = new TextAnimation(2700); renderCanvas(); } /* Step 2 of 3 */ /* Each animation object needs its own code */ /* The code for the various animations is now moved to seperate javascript files */ function renderCanvas() { ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); /* Step 3 of 3 */ /* Draw the animations */ for (let i = 0; i < animation.length; i++) { animation[i].renderObject(); } requestAnimationFrame(renderCanvas); } </script> </head> <body> <canvas id = "canvas" tabindex="1"> Your browser does not support the HTML5 'Canvas' tag. </canvas> </body> </html>

Animation.js file

/* Animation */ class Animation { constructor(animationStartDelay) { /* These variables are ALWAYS needed */ this.animationInterval = null; this.frameRate = 5; // change to suit the animation speed in milliseconds. Smaller numbers give a faster animation */ this.animationIsDisplayed = false; /* Start the animation */ setTimeout(this.start.bind(this), animationStartDelay); } /* Public functions */ start() { this.animationIsDisplayed = true; this.animationInterval = setInterval(this.update.bind(this), this.frameRate); } stop() { this.animationIsDisplayed = true; clearInterval(this.animationInterval); this.animationInterval = null; // set to null when not running } stopAndHide() { this.stop(); this.animationIsDisplayed = false; } renderObject() { if (this.animationIsDisplayed) { this.render(); } } /* update() and render() will be different for each animation */ update() { } render() { } }

ImageAnimation.js file

/* ImageAnimation object */ class ImageAnimation extends Animation { constructor(animationStartDelay) { super(animationStartDelay, 5); /* These variables depend on the animation */ this.x = 0; this.y = 0; this.size = 0; } update() { this.size++; if (this.size >= canvas.width) { this.stop(); } } render() { ctx.drawImage(image, this.x, this.y, this.size, this.size); } }

TextAnimation.js file

/* TextAnimation object */ class TextAnimation extends Animation { constructor(animationStartDelay) { super(animationStartDelay, 5); /* These variables depend on the animation */ this.x = canvas.width; this.minX = 35; this.y = 300; this.message = "DkIT"; this.fontSize = 200; this.colour = "red"; } update() { this.x--; if (this.x === this.minX) { this.stop(); } } render() { ctx.fillStyle = this.colour; ctx.font = this.fontSize + "px Times Roman"; ctx.fillText(this.message, this.x, this.y); } }

Note that the animation code in the examples that follow in these notes has been kept with the main code. This is to make it easy for students to cut and paste the code. In the real-world, they should be placed in seperate files.

Multiple Objects

We can give an object a set of initial (constructor) values. In the example above, we can pass in details of the text colour, font size, message, etc., as shown below.

Example of an object oriented animation with constructor parameters (Run Example)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>
            let canvas = null;
            let ctx = null;

            let image = new Image();
            image.src = "images/dkit04.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new ImageAnimation(0);
                animation[1] = new TextAnimation(2700, canvas.width, 35, 300, "DkIT", 200, "red");

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation object needs its own code */
            /******************************************************************************/
            /* Animation */
            class Animation
            {
                constructor(animationStartDelay, frameRate)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;
                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                /* update() and render() will be different for each animation */
                update()
                {
                }

                render()
                {
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* ImageAnimation object */
            class ImageAnimation extends Animation
            {
                constructor(animationStartDelay)
                {
                    super(animationStartDelay, 5)

                    /* These variables depend on the animation */
                    this.x = 0;
                    this.y = 0;
                    this.size = 0;
                }

                update()
                {
                    this.size++;

                    if (this.size >= canvas.width)
                    {
                        this.stop();
                    }
                }

                renderObject()
                {
                    this.render();
                }

                render()
                {
                    ctx.drawImage(image, this.x, this.y, this.size, this.size);
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* TextAnimation object */
            class TextAnimation extends Animation
            {
                constructor(animationStartDelay, startX, endX, y, message, fontSize, colour)
                {
                    super(animationStartDelay, 5);

                    /* These variables depend on the animation */
                    this.x = startX;
                    this.minX = endX;
                    this.y = y;
                    this.message = message;
                    this.fontSize = fontSize;
                    this.colour = colour;
                }              

                update()
                {
                    this.x--;
                    if (this.x === this.minX)
                    {
                        this.stop();
                    }
                }

                render()
                {
                    ctx.fillStyle = this.colour;
                    ctx.font = this.fontSize + "px Times Roman";
                    ctx.fillText(this.message, this.x, this.y);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Using parameters to set an object's initial behaviour allows us to make multiple instances of an object. In the example below, we change the way that the text looks for each of three words. Note that we only need to develop the object code once. The difference between each object is determined by the set of parameters that we send to each object.

Example that shows multiple instances of an object Run Example

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>
            let canvas = null;
            let ctx = null;

            let image = new Image();
            image.src = "images/dkit04.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new TextAnimation(0, canvas.width, 135, 100, "Welcome", 60, "red");
                animation[1] = new TextAnimation(2300, canvas.width,  135, 300, "to", 300, "green");
                animation[2] = new TextAnimation(4300, canvas.width, 1, 480, "DkIT", 230, "blue");

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation object needs its own code */
            /******************************************************************************/
            /* Animation */
            class Animation
            {
                constructor(animationStartDelay, frameRate)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;
                    
                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                /* update() and render() will be different for each animation */
                update()
                {
                }

                render()
                {
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* TextAnimation object */
            class TextAnimation extends Animation
            {
                constructor(animationStartDelay, startX, endX, y, message, fontSize, colour)
                {
                    super(animationStartDelay, 5);

                    /* These variables depend on the animation */
                    this.x = startX;
                    this.minX = endX;
                    this.y = y;
                    this.message = message;
                    this.fontSize = fontSize;
                    this.colour = colour;
                }

                update()
                {
                    this.x--;
                    if (this.x === this.minX)
                    {
                        this.stop();
                    }
                }

                render()
                {
                    ctx.fillStyle = this.colour;
                    ctx.font = this.fontSize + "px Times Roman";
                    ctx.fillText(this.message, this.x, this.y);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Object State Data

Object state data should be set as constructor parameters where possible. If should only be initialised as local object data if it cannot be different for different versions of the object. To achieve this, you should always look at the data in the constructor() method and decide if it can be variable or if it is fixed. For example, if you were asked to write code to get an image to pulse by a distance of 20 pixels from full canvas size, you might produce the code below.

Code to pulsate an image, as shown here (Run Example).

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>                          
            let canvas = null;
            let ctx = null;

            let backgroundImage = new Image();
            backgroundImage.src = "images/dkit03.png";

            let image = new Image();
            image.src = "images/dkit01.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new PulsatingImage(0);

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation needs its own code */
            /******************************************************************************/
            /* Animation */
            class Animation
            {
                constructor(animationStartDelay, frameRate)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;

                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                /* update() and render() will be different for each animation */
                update()
                {
                }

                render()
                {
                }
            }
            /******************************************************************************/


            /******************************************************************************/
            /* PulsatingImage Object */
            class PulsatingImage extends Animation
            {
                constructor(animationStartDelay)
                {
                    super(animationStartDelay, 15);

                    /* These variables depend on the animation */
                    this.pulseNumberOfSteps = 20;
                    this.startX = 0;
                    this.x = 0;
                    this.y = 0;
                    this.size = canvas.width;                   

                    this.increment = 1;
                }

                update()
                {
                    this.x += this.increment;
                    this.y += this.increment;
                    this.size = canvas.width - (this.x * 2);


                    if (this.x >= (this.startX + this.pulseNumberOfSteps))
                    {
                        this.increment = -1;
                    } 
                    else if (this.x <= this.startX)
                    {
                        this.increment = 1;
                    }
                }

                render()
                {
                    ctx.drawImage(image, this.x, this.y, this.size, this.size);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

From the above example, we can see that the ImageAnimation constructor contains the data below:

                constructor(animationStartDelay)
                {
                    super(animationStartDelay, 15);

                    /* These variables depend on the animation */
                    this.pulseNumberOfSteps = 20;
                    this.startX = 0;
                    this.x = 0;
                    this.y = 0;
                    this.size = canvas.width;                   

                    this.increment = 1;
                }

We can pass pulseNumberOfSteps, startX, startY and size as parameters to the object. We do not need to change increment, as it will always start as 1. This will result in the version of the code below:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>
            let canvas = null;
            let ctx = null;

            let backgroundImage = new Image();
            backgroundImage.src = "images/dkit03.png";

            let image = new Image();
            image.src = "images/dkit01.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new PulsatingImage(0, 20, 0, 0, canvas.width);

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation needs its own code */
            /******************************************************************************/
            /* Animation */
            class Animation
            {
                constructor(animationStartDelay, frameRate)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;

                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                /* update() and render() will be different for each animation */
                update()
                {
                }

                render()
                {
                }
            }


            /* PulsatingImage Object */
            class PulsatingImage extends Animation
            {
                constructor(animationStartDelay, pulseNumberOfSteps, startX, startY, size)
                {
                    super(animationStartDelay, 15);

                    /* These variables depend on the animation */
                    this.pulseNumberOfSteps = pulseNumberOfSteps;
                    this.startX = startX;
                    this.x = startX;
                    this.y = startY;
                    this.size = size;

                    this.increment = 1;
                }

                update()
                {
                    this.x += this.increment;
                    this.y += this.increment;
                    this.size = canvas.width - (this.x * 2);


                    if (this.x >= (this.startX + this.pulseNumberOfSteps))
                    {
                        this.increment = -1;
                    }
                    else if (this.x <= this.startX)
                    {
                        this.increment = 1;
                    }
                }

                render()
                {
                    ctx.drawImage(image, this.x, this.y, this.size, this.size);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Both versions of the code above do the exact same thing. However, the second version is much more useful.

Explain why the second version of the code (with the object state data passed in as parameters to the object) is more useful than the first version of the code.

Write code to rotate an image, as shown here.

The variable ctx.globalAlpha can be used to control the opacity of a canvas drawing. Amend the code from the previous question so that the rotating image fades in, as shown here.

Write code to produce a scrolling background image, as shown here.

Write code to produce a graphical countdown timer, as shown here.

If an animation consists of various related sub-animations, then it can makes sense to control all of the sub-animations with the same timer.

Write code to animate four images as shown here. Note that, as all four images are always the same size as each other, it is possible to use only one timer.

Amend the above code to animate four parts of the same image, as shown here. Hint: use ctx.drawImage(image, clipX, clipY, clipWidth, clipHeight, x, y, width, height). This allows you draw a clipped sub-section of an image.

Write code to make a slider with four images, as shown here.

Write code to make a square slider with four images, as shown here.

Write code to make a diagonal slider with four images, as shown here.

Sprite Animations

A sprite animation is made up of a set of sub-images that are combined into one master-image, as shown below.

In order to animate a sprite, we need to be able step through each sub-image in turn. This can be done using the code below:

const NUMBER_OF_SPRITES = 74; // the number of sprites in the sprite image
const NUMBER_OF_COLUMNS_IN_SPRITE_IMAGE = 9; // the number of columns in the sprite image
const NUMBER_OF_ROWS_IN_SPRITE_IMAGE = 9; // the number of columns in the sprite sprite
const START_ROW = 0;
const START_COLUMN = 0;

let currentSprite = 0; // the current sprite to be displayed from the sprite image  
let row = START_ROW; // current row in sprite image
let column = START_COLUMN; // current column in sprite image

this.update = update;
function update()
{
    if(currentSprite === NUMBER_OF_SPRITES) 
    { 
        stopAndHide(); 
    } 
    currentSprite++; 

    column++; 
    if (column >= NUMBER_OF_COLUMNS_IN_SPRITE_IMAGE) 
    { 
       column = 0; 
       row++; 
    }
}

The code below showns an animation of the explosion sprite (Run Example)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Worked example from lecture notes</title>
        <style>
            #canvas
            {
                border:1px solid black;
                width:500px;
                height:500px;
            }

            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>
            let canvas = null;
            let ctx = null;

            let explosionImage = new Image();
            explosionImage.src = "images/explosion.png";

            /* Animation array */
            let animation = [];

            window.onload = onAllAssetsLoaded;
            document.write("<div id='loadingMessage'>Loading...</div>");
            function onAllAssetsLoaded()
            {
                // stopAndHide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden";

                canvas = document.getElementById("canvas");
                ctx = canvas.getContext("2d");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;

                /* Step 1 of 3 */
                /* Each animation needs to be declared as an object */
                animation[0] = new ExplosionAnimation(0, 150, 250, 200);            // there are two explosions in this example
                animation[1] = new ExplosionAnimation(1000, 200, 300, 100);

                renderCanvas();
            }


            /* Step 2 of 3 */
            /* Each animation needs its own code */
            /******************************************************************************/
            /* Animation */
            class Animation
            {
                constructor(animationStartDelay, frameRate)
                {
                    /* These variables are ALWAYS needed */
                    this.animationInterval = null;
                    this.frameRate = frameRate; // change to suit the animation frameRate in milliseconds. Smaller numbers give a faster animation */
                    this.animationIsDisplayed = false;

                    /* Start the animation */
                    setTimeout(this.start.bind(this), animationStartDelay);
                }

                /* Public functions */
                start()
                {
                    this.animationIsDisplayed = true;
                    this.animationInterval = setInterval(this.update.bind(this), this.frameRate);
                }

                stop()
                {
                    this.animationIsDisplayed = true;
                    clearInterval(this.animationInterval);
                    this.animationInterval = null; // set to null when not running           
                }

                stopAndHide()
                {
                    this.stop();
                    this.animationIsDisplayed = false;
                }

                renderObject()
                {
                    if (this.animationIsDisplayed)
                    {
                        this.render();
                    }
                }

                /* update() and render() will be different for each animation */
                update()
                {
                }

                render()
                {
                }
            }


            /* ImageAnimation Object */
            class ExplosionAnimation extends Animation
            {
                constructor(animationStartDelay, centreX, centreY, size)
                {
                    super(animationStartDelay, 40);

                    /* These variables depend on the animation */
                    this.centreX = centreX;
                    this.centreY = centreY;
                    this.size = size;

                    this.NUMBER_OF_SPRITES = 74; // the number of sprites in the sprite image
                    this.NUMBER_OF_COLUMNS_IN_SPRITE_IMAGE = 9; // the number of columns in the sprite image
                    this.NUMBER_OF_ROWS_IN_SPRITE_IMAGE = 9; // the number of rows in the sprite image	
                    this.START_ROW = 0;
                    this.START_COLUMN = 0;
                    this.currentSprite = 0; // the current sprite to be displayed from the sprite image  
                    this.row = this.START_ROW; // current row in sprite image
                    this.column = this.START_COLUMN; // current column in sprite image

                    this.SPRITE_WIDTH = 100;
                    this.SPRITE_HEIGHT = 100;
                }

                update()
                {
                    if (this.currentSprite === this.NUMBER_OF_SPRITES)
                    {
                        this.stopAndHide();
                    }
                    this.currentSprite++;
                    
                    this.column++;
                    if (this.column >= this.NUMBER_OF_COLUMNS_IN_SPRITE_IMAGE)
                    {
                        this.column = 0;
                        this.row++;
                    }
                    
                    this.SPRITE_WIDTH = (explosionImage.width / this.NUMBER_OF_COLUMNS_IN_SPRITE_IMAGE);
                    this.SPRITE_HEIGHT = (explosionImage.height / this.NUMBER_OF_ROWS_IN_SPRITE_IMAGE);
                }

                render()
                {
                    ctx.drawImage(explosionImage, this.column * this.SPRITE_WIDTH, this.row * this.SPRITE_WIDTH, this.SPRITE_WIDTH, this.SPRITE_HEIGHT, this.centreX - parseInt(this.size / 2), this.centreY - parseInt(this.size / 2), this.size, this.size);
                }
            }
            /******************************************************************************/


            function renderCanvas()
            {
                requestAnimationFrame(renderCanvas);
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                /* Step 3 of 3 */
                /* Draw the animations */
                for (let i = 0; i < animation.length; i++)
                {
                    animation[i].renderObject();
                }
            }
        </script>
    </head>

    <body>
        <canvas id = "canvas" tabindex="1">
            Your browser does not support the HTML5 'Canvas' tag.
        </canvas>
    </body>
</html>

Write code to animate a sprite of a bird flying, as shown here.

Write code to animate a sprite of a bird flying on a scrolling background, as shown here.

A sprite's first frame can be located at any row and column in the sprite image. The FIRST_ROW and FIRST_COLUMN constants in the example above deal with this situation. A sprite image can be oriented the wrong way for the purposes of a partiular animation. Write code that rotates the green tank in the sprite image below by 90 degrees clockwise before it displays, as shown here.

In the above example, the sprite images are in reverse order. This is giving the visual effect of the tank tracks moving in reverse. Amend the above code to display the sprite images in reverse order, so that the tank tracks look as though they are moving forward, as shown here.

 
<div align="center"><a href="../../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>