Skip to content

Live Coding: Timeouts, Debugging, and Packages

Timing in JavaScript

There are a few functions in JavaScript which deal with timing the calling of functions:

// calls function after duration milliseconds from now
setTimeout(function, duration);

// calls function after every duration milliseconds
setInterval(function, duration);

// calls a callback function at a frequency matching the display refresh rate
requestAnimationFrame(callback);
// calls function after duration milliseconds from now
setTimeout(function, duration);

// calls function after every duration milliseconds
setInterval(function, duration);

// calls a callback function at a frequency matching the display refresh rate
requestAnimationFrame(callback);

These can be using in conjunction with CSS styles on document object model elements to create animations.

Here's an example using setTimeout():

js
let cx = document.querySelector("canvas").getContext("2d");

setTimeout(function() {
  getEmojis("https://api.github.com/emojis")
  .then(emojiData => {
    for(let i=0; i<25; i++){
    let faceImg = new Image();
    faceImg.src = emojiData.smiling_face_with_three_hearts;
    faceImg.onload = function(){
      let locX = Math.round(Math.random() * (736 - 64) + 64);
      let locY = Math.round(Math.random() * (536 - 64) + 64);
      faceImg.style.left = locX;
      faceImg.style.top = locY;
      cx.drawImage(faceImg, locX, locY, 64, 64);
      }
    }
  })
}, 3000);

async function getEmojis(file) {
  let myObject = await fetch(file);
  let myJSobj = await myObject.json();
  return myJSobj;
}
let cx = document.querySelector("canvas").getContext("2d");

setTimeout(function() {
  getEmojis("https://api.github.com/emojis")
  .then(emojiData => {
    for(let i=0; i<25; i++){
    let faceImg = new Image();
    faceImg.src = emojiData.smiling_face_with_three_hearts;
    faceImg.onload = function(){
      let locX = Math.round(Math.random() * (736 - 64) + 64);
      let locY = Math.round(Math.random() * (536 - 64) + 64);
      faceImg.style.left = locX;
      faceImg.style.top = locY;
      cx.drawImage(faceImg, locX, locY, 64, 64);
      }
    }
  })
}, 3000);

async function getEmojis(file) {
  let myObject = await fetch(file);
  let myJSobj = await myObject.json();
  return myJSobj;
}

Here's an example using setInterval() to create trails:

js
let cx = document.querySelector("canvas").getContext("2d");
let locs = [];


getEmojis("https://api.github.com/emojis")
.then(emojiData => {
  for(let i=0; i<25; i++){
    let locX = Math.round(Math.random() * (736 - 64) + 64);
    let locY = Math.round(Math.random() * (536 - 64) + 64);
    locs.push([locX, locY]);
  }
  for(let i=0; i<25; i++){
    let newFace = new Emoji(locs[i][0], locs[i][1], emojiData.smiling_face_with_three_hearts);
    newFace.faceImg.onload = function(){
      setInterval(function() {
        newFace.update();
        newFace.display();
        cx.drawImage(newFace.faceImg, newFace.locX, newFace.locY, 64, 64);
      }, 120);
    }

  }
});

async function getEmojis(file) {
  let myObject = await fetch(file);
  let myJSobj = await myObject.json();
  return myJSobj;
}

class Emoji{
  constructor(locX, locY, source){
    this.faceImg = new Image();
    this.faceImg.src = source;
    this.locX = locX;
    this.locY = locY;
    this.stepX = Math.random() * 5;
    this.stepY = Math.random() * 5;
  }
  update(){
    this.locX += this.stepX;
    if(this.locX>736){
      this.locX = 64;
    }
    this.locY += this.stepY;
    if(this.locY>536){
      this.locY = 64;
    }
  }
  display(){
    this.faceImg.style.left = this.locX;
    this.faceImg.style.top = this.locY;
  }
}
let cx = document.querySelector("canvas").getContext("2d");
let locs = [];


getEmojis("https://api.github.com/emojis")
.then(emojiData => {
  for(let i=0; i<25; i++){
    let locX = Math.round(Math.random() * (736 - 64) + 64);
    let locY = Math.round(Math.random() * (536 - 64) + 64);
    locs.push([locX, locY]);
  }
  for(let i=0; i<25; i++){
    let newFace = new Emoji(locs[i][0], locs[i][1], emojiData.smiling_face_with_three_hearts);
    newFace.faceImg.onload = function(){
      setInterval(function() {
        newFace.update();
        newFace.display();
        cx.drawImage(newFace.faceImg, newFace.locX, newFace.locY, 64, 64);
      }, 120);
    }

  }
});

async function getEmojis(file) {
  let myObject = await fetch(file);
  let myJSobj = await myObject.json();
  return myJSobj;
}

class Emoji{
  constructor(locX, locY, source){
    this.faceImg = new Image();
    this.faceImg.src = source;
    this.locX = locX;
    this.locY = locY;
    this.stepX = Math.random() * 5;
    this.stepY = Math.random() * 5;
  }
  update(){
    this.locX += this.stepX;
    if(this.locX>736){
      this.locX = 64;
    }
    this.locY += this.stepY;
    if(this.locY>536){
      this.locY = 64;
    }
  }
  display(){
    this.faceImg.style.left = this.locX;
    this.faceImg.style.top = this.locY;
  }
}

requestAnimationFrame() allows you to execute code on the next available screen repaint, syncing with the user’s browser and hardware to make changes to the screen:

js
let raindrops = [];
let canvas = document.querySelector("canvas");
canvas.width = window.innerWidth-20;
canvas.height = window.innerHeight-20;
let cx = canvas.getContext("2d");
let stopAnim;

class rainDroplet {
    constructor() {
        this.initX = Math.random() * canvas.width;
        this.initY = Math.random() * canvas.height;
        this.speed = Math.random() * 8;
        //this.colour = `rgb(${Math.floor(Math.random()*255)} ${Math.floor(Math.random()*255)} ${Math.floor(Math.random()*255)})`;
    }

    drawRose(){
        cx.moveTo(this.initX, this.initY);
        cx.beginPath();
        for (let i = 0; i < (Math.PI * 2); i += 0.1) {
            let r = 20 * Math.abs(Math.cos(4 * i));
            let x = (r * Math.cos(i)) + this.initX;
            let y = (r * Math.sin(i)) + this.initY;
            cx.lineTo(x, y/*, x1, y1*/);
            cx.stroke();
            cx.strokeStyle = "pink";
            //cx.strokeStyle = this.colour;
        }
    }

    moveRose(){
        this.initY += this.speed;
        if(this.initY > canvas.height + 20){
            this.initY = -this.initY;
        }
    }
    
}

function initialise(){
    for(let i=0; i<100; i++){
        let raindrop = new rainDroplet();
        raindrops.push(raindrop);
    }
}

function draw(){
    for(let i = 0; i < raindrops.length; i++){
        raindrops[i].drawRose();
    }
}

function update(){
    for(let i = 0; i < raindrops.length; i++){
        cx.clearRect(0, 0, canvas.width, canvas.height);
        raindrops[i].moveRose();
        //console.log(raindrops[i].initY + " and " + raindrops[i].initX);
    }
    draw();
    window.requestAnimationFrame(update);
}

initialise();
update();
let raindrops = [];
let canvas = document.querySelector("canvas");
canvas.width = window.innerWidth-20;
canvas.height = window.innerHeight-20;
let cx = canvas.getContext("2d");
let stopAnim;

class rainDroplet {
    constructor() {
        this.initX = Math.random() * canvas.width;
        this.initY = Math.random() * canvas.height;
        this.speed = Math.random() * 8;
        //this.colour = `rgb(${Math.floor(Math.random()*255)} ${Math.floor(Math.random()*255)} ${Math.floor(Math.random()*255)})`;
    }

    drawRose(){
        cx.moveTo(this.initX, this.initY);
        cx.beginPath();
        for (let i = 0; i < (Math.PI * 2); i += 0.1) {
            let r = 20 * Math.abs(Math.cos(4 * i));
            let x = (r * Math.cos(i)) + this.initX;
            let y = (r * Math.sin(i)) + this.initY;
            cx.lineTo(x, y/*, x1, y1*/);
            cx.stroke();
            cx.strokeStyle = "pink";
            //cx.strokeStyle = this.colour;
        }
    }

    moveRose(){
        this.initY += this.speed;
        if(this.initY > canvas.height + 20){
            this.initY = -this.initY;
        }
    }
    
}

function initialise(){
    for(let i=0; i<100; i++){
        let raindrop = new rainDroplet();
        raindrops.push(raindrop);
    }
}

function draw(){
    for(let i = 0; i < raindrops.length; i++){
        raindrops[i].drawRose();
    }
}

function update(){
    for(let i = 0; i < raindrops.length; i++){
        cx.clearRect(0, 0, canvas.width, canvas.height);
        raindrops[i].moveRose();
        //console.log(raindrops[i].initY + " and " + raindrops[i].initX);
    }
    draw();
    window.requestAnimationFrame(update);
}

initialise();
update();

Debugging with console.log

For all debugging, you need two things:

  • 🗺️ a map of the path taken through the program

  • 🔦 a torch to shine a light where JavaScript went astray

For the map, you can use your knowledge of the order in which the program runs. If you’re not sure, start at the very beginning and work it out bit by bit.

For the torch, you have several options:

  • Error messages (syntax, runtime, logical) provided in the console when the program runs

  • console.log statements placed at possible wrong turns

  • The debugger statement and related tools in editors and browsers

Here’s a buggy program to practice on:

html
<script src="main.js" defer></script>
<script src="main.js" defer></script>
html
<form action="" id="background-color-form" method="post">
  <div>
    <label for="color">mustard, red, or blue?</label>
  </div>
  <div>
    <select id="color" name="color">
      <option value="mustard">Mustard</option>
      <option value="red">Red</option>
      <option value="blue">Blue</option>
    </select>
    <button type="submit">Submit</button>
  </div>
</form>
<form action="" id="background-color-form" method="post">
  <div>
    <label for="color">mustard, red, or blue?</label>
  </div>
  <div>
    <select id="color" name="color">
      <option value="mustard">Mustard</option>
      <option value="red">Red</option>
      <option value="blue">Blue</option>
    </select>
    <button type="submit">Submit</button>
  </div>
</form>
js
// app.js

let form = document.querySelector('#form')

function changeColor(event) [
  event,preventDefault()
  let colorSelect = form.querySelector('#color')
  let color = colorSelect.value
  for square = document.querySelector('.square')
  square.className = `square ${color}`
}

changeForm.addEventListener('submit', changeColor)
// app.js

let form = document.querySelector('#form')

function changeColor(event) [
  event,preventDefault()
  let colorSelect = form.querySelector('#color')
  let color = colorSelect.value
  for square = document.querySelector('.square')
  square.className = `square ${color}`
}

changeForm.addEventListener('submit', changeColor)

Installing an NPM package

Example repository

To add a package like chalk to your project, run this at the command line:

bash
npm install chalk
npm install chalk

This will automatically create or update the package.json file with the appropriate line in the dependencies array:

json
{
  "type": "module",
  "dependencies": {
    "chalk": "^5.3.0"
  }
}
{
  "type": "module",
  "dependencies": {
    "chalk": "^5.3.0"
  }
}

TIP

You may need to add "type": "module", manually.

Then import the package according to its documentation in your JavaScript:

js
import chalk from 'chalk'

console.log(chalk.green('Green!'))
import chalk from 'chalk'

console.log(chalk.green('Green!'))

Telling Git to ignore source code from NPM packages

In your .gitignore file:

node_modules
node_modules

Content CC BY 4.0 | Code AGPL 3.0