Week 3 — Animation, Inputs & Media

A still picture is not a game. This week the ball moves. You will animate with timers, bounce off walls, let the player change the speed through form inputs, and draw images and video onto the canvas.

🎯 Objectives

  • Repeat code over time with setInterval and stop it with clearInterval.
  • Erase and redraw the canvas every frame so motion looks smooth.
  • Represent motion with a position (ballx, bally) and velocity (ballvx, ballvy).
  • Detect collisions with the walls and reverse velocity.
  • Read numbers from form fields and convert with Number().
  • Use HTML5 input validation (type="number", min, max).
  • Draw images with drawImage and embed video with <video>.

🛠 Weekly tasks

  1. Objective 1 — repeat code over time with setInterval and stop it with clearInterval: Activity 1 + build bouncingballinputs.html.
  2. Objective 2 — erase and redraw the canvas every frame: Activity 2 + build bouncingballinputs.html.
  3. Objective 3 — represent motion with a position and a velocity: Activity 3 + build bouncingballinputs.html.
  4. Objective 4 — detect the wall and reverse the velocity: Activity 4 + build bouncingballinputs.html.
  5. Objective 5 — read numbers from the form with Number(): Activity 5 + build bouncingballinputs.html.
  6. Objective 6 — HTML5 input validation (type="number", min, max): Activity 6 + build bouncingballinputsvalidate.html.
  7. Objective 7 — draw images with drawImage and embed video with <video>: Activity 7 + build bouncingcandybackground.html and bouncingVideoOk2.html.
  8. Build your own bouncingballinputs_yourname.html — using everything you have learned; add gravity by increasing ballvy a little each frame. (All objectives)

🎓 Lecture activity Open lecture slides →

A 90-minute session of 35 slides. Each concept is taught four ways: the big idea, a short example with its live output, a line-by-line explanation, and a 5-minute “try it” task. The lab work at 1:00 uses the Lab activity below.

Session plan

  1. 0:0010 minWarm-upReview questions on last week
  2. 0:1010 minGame & objectivesThis week’s game, objectives and key words
  3. 0:2035 minConceptsBig idea → code with live output → line by line → try it
  4. 0:555 minCheck understandingCommon mistakes and a 4-question quiz
  5. 1:0025 minLabBuild the files in the Lab activity below
  6. 1:255 minWrap-upBuild-your-own task, marking guide and recap

Concepts this week

  • 1. The animation loop — Erase, move, draw, repeat
  • 2. Collision & bouncing — Reverse the velocity at a wall
  • 3. Input & validation — Check input before you use it
  • 4. Gradients & images — Blend colours and stamp pictures

The slides open as a page on this site. Press F for full screen, the arrow keys to move, N for speaker notes, and P to save as PDF (turn on “Background graphics”).

🧪 Lab activity: work through the files Printable lab sheet

Create a folder named Week3 on your PC. Work through the files below in order, creating each one and writing the code shown. Some files need the images and video from the Materials panel below. Save each file and refresh the browser.

Want a head start? A scaffold file starter.html is provided — choose it in the preview dropdown, copy it into your Week3 folder, and complete its TODOs.

  1. Create your Week3 folder

    On your own PC — do this once before starting.

    1. Create a folder named Week3 on your PC.
    2. Download the materials below into it (candy.png, reunion.jpg, pearl.jpg, readers.jpg, the talk video files).
    3. Open your Week3 folder in VS Code or Sublime Text.
  2. bouncingballinputs.html Objectives 1, 2, 3, 4, 5

    Week3\bouncingballinputs.html — a bouncing ball whose speed the player can change.

    1. [Obj 1, 5] Create bouncingballinputs.html and add the page skeleton, the <style> rule for the form, the canvas (with fallback text) and the input form. Notice onLoad="init();" on <body> and onSubmit="return change();" on the form — these call the functions you write next.
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <title>Bouncing Ball with inputs</title>
        <style>
          form { width: 330px; margin: 20px; background-color: brown; padding: 20px; }
        </style>
      </head>
      <body onLoad="init();">
        <canvas id="canvas" width="400" height="300">
          Your browser doesn't support the HTML5 element canvas.
        </canvas>
        <br />
        <form name="f" id="f" onSubmit="return change();">
          Horizontal velocity <input name="hv" id="hv" value="4" type="number" min="-10" max="10" />
          <br>
          Vertical velocity <input name="vv" id="vv" value="8" type="number" min="-10" max="10" />
          <input type="submit" value="CHANGE" />
        </form>
      </body>
      </html>
    2. [Obj 3] Add a <script> block and declare the box, ball and velocity variables. The four boundary values are calculated from the box and the ball radius, so the ball bounces when its edge (not its centre) reaches the wall.
      var boxx = 20, boxy = 30, boxwidth = 350, boxheight = 250;
      var ballrad = 10;
      var boxboundx = boxwidth + boxx - ballrad;
      var boxboundy = boxheight + boxy - ballrad;
      var inboxboundx = boxx + ballrad;
      var inboxboundy = boxy + ballrad;
      var ballx = 50, bally = 60;
      var ctx;
      var ballvx = 4, ballvy = 8;
    3. [Obj 1, 2] Add init(). It is called by onLoad on the body: it gets the 2D context, sets the line width and fill colour, draws the first frame, then uses setInterval to repeat moveball() every 100 ms (10 frames a second).
      function init() {
        ctx = document.getElementById("canvas").getContext("2d");
        ctx.lineWidth = ballrad;
        ctx.fillStyle = "rgb(200,0,50)";
        moveball();
        setInterval(moveball, 100);
      }
    4. [Obj 2, 3] Add moveball() — the frame: erase the old drawing, move the ball, then draw the ball and the box outline again. Without clearRect the old circles would smear across the canvas.
      function moveball() {
        ctx.clearRect(boxx, boxy, boxwidth, boxheight);
        moveandcheck();
        ctx.beginPath();
        ctx.arc(ballx, bally, ballrad, 0, Math.PI * 2, true);
        ctx.fill();
        ctx.strokeRect(boxx, boxy, boxwidth, boxheight);
      }
    5. [Obj 3, 4] Add moveandcheck(). It works out the next position, reverses the velocity when that position crosses a wall, and snaps the ball back onto the wall so it can never escape the box.
      function moveandcheck() {
        var nballx = ballx + ballvx;
        var nbally = bally + ballvy;
      
        if (nballx > boxboundx) { ballvx = -ballvx; nballx = boxboundx; }
        if (nballx < inboxboundx) { nballx = inboxboundx; ballvx = -ballvx; }
        if (nbally > boxboundy) { nbally = boxboundy; ballvy = -ballvy; }
        if (nbally < inboxboundy) { nbally = inboxboundy; ballvy = -ballvy; }
      
        ballx = nballx;
        bally = nbally;
      }
    6. [Obj 5] Add change(). It reads the two boxes with Number() and returns false so the form does not reload the page.
      function change() {
        ballvx = Number(document.f.hv.value);
        ballvy = Number(document.f.vv.value);
        return false;
      }
    7. Save and refresh: the ball bounces inside the box. Type new values (for example -6 and 3) and press CHANGE.
  3. bouncingballinputsvalidate.html Objective 6

    Week3\bouncingballinputsvalidate.html — the same ball, plus HTML5 input validation.

    1. [Obj 6] Copy bouncingballinputs.html to bouncingballinputsvalidate.html. The ball, the canvas and the form stay exactly the same — you only add validation styling, so there is very little new to type.
    2. [Obj 6] Add these two selectors to the <style> block. A number input becomes :valid or :invalid automatically, based on its type, min and max.
      input:valid   { background: green; }
      input:invalid { background: red; }
    3. [Obj 6] Save and refresh. Both boxes are green, because 4 and 8 are inside the allowed range -10 to 10.
    4. [Obj 6] Now make a box invalid: type 20 (past max="10"), clear it, or type letters. It turns red, and pressing CHANGE is blocked by the browser with a validation message — change() never runs.
    5. Try it: add required to one input and submit it empty.
      <input name="hv" type="number" min="-10" max="10" required />
  4. bouncingcandybackground.html Objectives 1, 2, 7

    Week3\bouncingcandybackground.html — an image ball bouncing over a photo, with STOP / RESUME.

    1. [Obj 1, 7] Create bouncingcandybackground.html and add the page. The box now fills the whole canvas, and the two <img> tags at the end are hidden by img { visibility: hidden; } — they are only there so the images start loading.
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Bouncing cotton candy!</title>
        <style>
          form { width: 330px; margin: 20px; background-color: #b10515; padding: 20px; }
          img  { visibility: hidden; }
        </style>
      </head>
      <body onLoad="init();">
        Click STOP to stop the cotton candy, then RESUME to start it again.
        <br />
        You can change the velocities too. <br />
      
        <canvas id="canvas" width="400" height="300">
          This browser doesn't support the HTML5 canvas element.
        </canvas>
        <br />
        <form name="f" id="f" onSubmit="return change();">
          Horizontal velocity <input name="hv" id="hv" value="4" type="number" min="-10" max="10" />
          <br>
          Vertical velocity <input name="vv" id="vv" value="8" type="number" min="-10" max="10" />
          <input type="submit" value="CHANGE" />
          <button onClick="return stopcc();">STOP</button>
          <button onClick="return resume();">RESUME</button>
        </form>
      
        <img src="candy.png" />
        <img src="reunion.jpg" />
      </body>
      </html>
    2. [Obj 3, 7] Add the variables and the two images. stoppedx / stoppedy will remember the speeds while the animation is paused.
      var ballrad = 10;
      var boxx = 0, boxy = 0, boxwidth = 400, boxheight = 300;
      var boxboundx = boxwidth;
      var boxboundy = boxheight;
      var inboxboundx = boxx + ballrad;
      var inboxboundy = boxy + ballrad;
      var ballx = 50, bally = 60, ballvx = 4, ballvy = 8;
      
      var ctx, tid;
      var bkg = new Image();
      var ball = new Image();
      var stoppedx = ballvx;
      var stoppedy = ballvy;
    3. [Obj 1, 7] Add init(): get the context, set the two image sources, draw the first frame, and remember the timer id so STOP can cancel it.
      function init() {
        ctx = document.getElementById("canvas").getContext("2d");
        bkg.src = "reunion.jpg";
        ball.src = "candy.png";
        ctx.lineWidth = ballrad;
        moveball();
        tid = setInterval(moveball, 100);
      }
    4. [Obj 2, 7] Add moveball(). The background photo is drawn from a small region into the whole canvas; the candy image is scaled down to 388/10 by 435/10.
      function moveball() {
        ctx.clearRect(boxx, boxy, boxwidth, boxheight);
        moveandcheck();
        ctx.drawImage(bkg, 0, 0, 4000, 3000, 0, 0, 400, 300);
        ctx.drawImage(ball, 0, 0, 388, 435, ballx - ballrad, bally - ballrad, 388 / 10, 435 / 10);
        ctx.strokeRect(0, 0, 400, 300);
      }
    5. [Obj 4] Add moveandcheck() — the same clamped wall bounce you wrote for bouncingballinputs.html.
      function moveandcheck() {
        var nballx = ballx + ballvx;
        var nbally = bally + ballvy;
        if (nballx > boxboundx) { ballvx = -ballvx; nballx = boxboundx; }
        if (nballx < inboxboundx) { nballx = inboxboundx; ballvx = -ballvx; }
        if (nbally > boxboundy) { nbally = boxboundy; ballvy = -ballvy; }
        if (nbally < inboxboundy) { nbally = inboxboundy; ballvy = -ballvy; }
        ballx = nballx;
        bally = nbally;
      }
    6. [Obj 5] Add change() (read the inputs and remember the new speeds).
      function change() {
        ballvx = Number(document.f.hv.value);
        ballvy = Number(document.f.vv.value);
        stoppedx = ballvx;
        stoppedy = ballvy;
        return false;
      }
    7. [Obj 1] Add stopcc() and resume(). STOP cancels the timer but draws one last frame; RESUME restores the saved speeds and restarts the timer.
      function stopcc() {
        clearInterval(tid);
        stoppedx = ballvx;
        stoppedy = ballvy;
        moveball();
        return false;
      }
      function resume() {
        clearInterval(tid);
        ballvx = stoppedx;
        ballvy = stoppedy;
        moveball();
        tid = setInterval(moveball, 100);
        return false;
      }
    8. Save and refresh, then press STOP, change the velocities and press RESUME.
  5. bouncintballinputsimggradients.html Objectives 2, 3, 7

    Week3\bouncintballinputsimggradients.html — an image ball inside rainbow gradient walls.

    1. [Obj 2, 7] Create bouncintballinputsimggradients.html from the same skeleton, but give the page its own styling and form.
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <title>Bouncing Ball with inputs</title>
        <style>
          form { width: 330px; margin: 20px; background-color: #b10515; padding: 20px; }
        </style>
      </head>
      <body onLoad="init();">
        <canvas id="canvas" width="400" height="300"></canvas>
        <br />
        <form name="f" id="f" onSubmit="return change();">
          Horizontal velocity <input name="hv" id="hv" value="4" type="number" min="-10" max="10" />
          <br>
          Vertical velocity <input name="vv" id="vv" value="8" type="number" min="-10" max="10" />
          <input type="submit" value="CHANGE" />
        </form>
      </body>
      </html>
    2. [Obj 3, 7] Add the variables, the ball image and the hue array — each row is one red-green-blue colour of the rainbow.
      var boxx = 20, boxy = 30, boxwidth = 350, boxheight = 250;
      var ballrad = 10;
      var boxboundx = boxwidth + boxx - ballrad;
      var boxboundy = boxheight + boxy - ballrad;
      var inboxboundx = boxx + ballrad;
      var inboxboundy = boxy + ballrad;
      var ballx = 50, bally = 60, ballvx = 4, ballvy = 8;
      
      var ctx, grad, color;
      var img = new Image();
      img.src = "pearl.jpg";
      var hue = [
        [255,   0,   0],  // red
        [255, 255,   0],  // yellow
        [  0, 255,   0],  // green
        [  0, 255, 255],  // cyan
        [  0,   0, 255],  // blue
        [255,   0, 255]   // magenta
      ];
    3. [Obj 2] Add init(). It creates one linear gradient across the box, then loops over hue to add a colour stop for each row, so the walls become a rainbow. Looping over the array means you can add or remove colours without writing new code.
      function init() {
        ctx = document.getElementById("canvas").getContext("2d");
        grad = ctx.createLinearGradient(boxx, boxy, boxx + boxwidth, boxy + boxheight);
        for (var h = 0; h < hue.length; h++) {
          color = "rgb(" + hue[h][0] + "," + hue[h][1] + "," + hue[h][2] + ")";
          grad.addColorStop(h * 1 / hue.length, color);
        }
        ctx.fillStyle = grad;
        ctx.lineWidth = ballrad;
        moveball();
        setInterval(moveball, 100);
      }
    4. [Obj 2, 3, 7] Add moveball(). It erases the box, moves the ball, draws the pearl image, then paints the four walls with fillRect. The walls pick up the rainbow because ctx.fillStyle is the gradient.
      function moveball() {
        ctx.clearRect(boxx, boxy, boxwidth, boxheight);
        moveandcheck();
        ctx.drawImage(img, ballx - ballrad, bally - ballrad, 2 * ballrad, 2 * ballrad);
        ctx.fillRect(boxx, boxy, ballrad, boxheight);
        ctx.fillRect(boxx + boxwidth - ballrad, boxy, ballrad, boxheight);
        ctx.fillRect(boxx, boxy, boxwidth, ballrad);
        ctx.fillRect(boxx, boxy + boxheight - ballrad, boxwidth, ballrad);
      }
    5. [Obj 4] Add moveandcheck() (the same clamped wall bounce).
      function moveandcheck() {
        var nballx = ballx + ballvx;
        var nbally = bally + ballvy;
        if (nballx > boxboundx) { ballvx = -ballvx; nballx = boxboundx; }
        if (nballx < inboxboundx) { nballx = inboxboundx; ballvx = -ballvx; }
        if (nbally > boxboundy) { nbally = boxboundy; ballvy = -ballvy; }
        if (nbally < inboxboundy) { nbally = inboxboundy; ballvy = -ballvy; }
        ballx = nballx;
        bally = nbally;
      }
    6. [Obj 5] Add change() for the velocity inputs.
      function change() {
        ballvx = Number(document.f.hv.value);
        ballvy = Number(document.f.vv.value);
        return false;
      }
    7. Save and refresh.
  6. bouncingVideoOk2.html Objectives 2, 7

    Week3\bouncingVideoOk2.html — a <video> that bounces around the page.

    1. [Obj 7] Create bouncingVideoOk2.html. The video is wrapped in a <div id="con"> (this is the element we move), a background photo readers.jpg defines the bouncing area, and several <source> formats are given so every browser can play the video.
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <title>Bouncing Video</title>
        <style>
          #videoE { position: absolute; display: none; z-index: 1; }
          #con    { position: absolute; }
        </style>
      </head>
      <body onLoad="init();">
        <image id="AandF" src="readers.jpg" height="100%" />
      
        <div id="con">
          <video id="videoE" controls width="300">
            <source src="talk.theora.ogv" type="video/ogg" />
            <source src="talk.mp4video.mp4" type="video/mp4" />
            <source src="talk.webmvp8.webm" type="video/webm" />
            Sorry, your browser doesn't support embedded videos.
          </video>
        </div>
      
        <button onclick="startV()">Click to start</button>
      </body>
      </html>
    2. [Obj 7] Add the variables.
      var v, c, img;
      var iwidth, iheight, vwidth, vheight;
      var leftEdge, rightEdge, topEdge, botEdge;
      var ballx = 250, bally = 260, ballvx = 14, ballvy = 18;
    3. [Obj 7] Add init() (called by onLoad). It measures the background image and the video, then works out where the video must turn around — note the bounce area is smaller than the image, because the video has its own width and height.
      function init() {
        v = document.getElementById("videoE");
        c = document.getElementById("con");
        img = document.getElementById("AandF");
        iwidth = img.clientWidth;
        iheight = img.clientHeight;
        vwidth = v.videoWidth;
        vheight = v.videoHeight;
        leftEdge = 5;
        rightEdge = leftEdge + iwidth - 0.6 * vwidth;
        topEdge = 5;
        botEdge = topEdge + iheight - 0.6 * vheight;
      }
    4. [Obj 7] Add startV(). It plays and shows the video, places the container, and — when the video ends — rewinds and plays again so it loops forever.
      function startV() {
        v.play();
        v.style.display = "block";
        c.style.top = bally + "px";
        c.style.left = ballx + "px";
        v.addEventListener("ended", function () {
          v.currentTime = 0;
          v.play();
        });
        moveball();
        setInterval(moveball, 100);
      }
    5. [Obj 2] Add moveball() and moveandcheck(). The bounce clamps to the four edges, then the #con container is moved with CSS top / left — the same idea as the canvas, but with page elements instead.
      function moveball() {
        moveandcheck();
      }
      function moveandcheck() {
        var nballx = ballx + ballvx;
        var nbally = bally + ballvy;
        if (nballx < leftEdge) { ballvx = -ballvx; nballx = leftEdge; }
        if (nballx > rightEdge) { nballx = rightEdge; ballvx = -ballvx; }
        if (nbally > botEdge) { nbally = botEdge; ballvy = -ballvy; }
        if (nbally < topEdge) { nbally = topEdge; ballvy = -ballvy; }
        ballx = nballx;
        bally = nbally;
        c.style.top = bally + "px";
        c.style.left = ballx + "px";
      }
    6. Save and refresh, then press Click to start: the video plays and bounces around the photo.
Finished example output

The heart of the animation is moveandcheck(): it works out the next position, flips the velocity if that position would cross a wall, then stores the result.

📚 Files in this lab