Finora negli esempi ( Come creare un animazione in JavaScript), ho gestito solo forme grafiche, adesso vediamo come inserire nell'animazone delle immagini.
Usa i tasti cursore per muovere il quadrato bianco e i tasti WDSA per muovere il cerchio viola. Usa il mouse per muovere i 5 cerchi verdi.

Per gestire l'animazione delle immagini, per prima cosa aggiungo il nuovo oggetto:
//tipi di sprite gestiti (uso gli interi perchè sono più efficenti)
const SpriteType = {
  ...
  IMAGE: 4
};

function SpriteImage(x, y, src, deltaX, deltaY) {
  this.type = SpriteType.IMAGE;
  this.x = x; // x e y sempre riferiti al centro dell'oggetto
  this.y = y;
  this.w = 0;
  this.h = 0;
  this.src = src;
  this.image = null;
  this.deltaX = deltaX == null ? 0 : deltaX;
  this.deltaY = deltaY == null ? 0 : deltaY;
  this.animationType = (this.deltaX !== 0 || this.deltaY !== 0) ? AnimationType.LINEAR : AnimationType.NONE;
  this.alpha = 1;
  this.animationType = AnimationType.LINEAR;
}
In questo caso non devo gestire in modo specifico l'aggiornamento delle coordinate o le collisioni in quanto posso assimilarli a dei rettangolo. Però devo gestire come viene disegnata sul canvas modificando il metodo draw:
function draw() {

  arena.sprites.forEach(sprite => {
    ctx.globalAlpha = sprite.alpha;
    let x = 0;
    let y = 0;

    switch (sprite.type) {
    ...
      case SpriteType.IMAGE:
        x = sprite.x - sprite.w / 2;
        y = sprite.y - sprite.h / 2;
        ctx.drawImage(sprite.image, x, y);
        break;
   ...
    }
  });
}
Ultimo passo, gestire il caricamento delle immagini modificando il metodo createSprites:
function createSprites() {
  ...
  //array con le immagini da caricare casualmente
  const images = ['fantasmino.png', 'blue.png', 'pacman.png'];

  //aggiungo sprite casuali
  for (var i = 0; i < 40; i++) {
    ...
    if (t < .4) {
      const circle = new SpriteCircle(x, y, w2, null, color, deltaX, deltaY);
      circle.alpha = alpha;
      arena.addSprite(circle);
    } else if (t < .6) {
      const src = images[Math.random() * images.length | 0];
      const image = new SpriteImage(x, y, src, deltaX, deltaY);
      image.alpha = alpha;
      arena.addSprite(image);
    } else {
      const rect = new SpriteRect(x, y, w2 * 2, h2 * 2, null, color, deltaX, deltaY);
      rect.alpha = alpha;
      arena.addSprite(rect);
    }
  }
  ..
  // mi assicuro che vengano caricate le immagini
  loadResources();
}
l'ultima istruzione, loadResources(), serve per caricare le immagini..

Essendo il caricamento delle immagini asincrono, prima cerco quali sprite sono di tipo image e ricavo il totale. Successivamente invoco il caricamento (loadImage) e al completamento diminuisco il contatore del totale delle immagini. Quando il totale è zero ho tutte le immagini caricate e posso invocare l'animazione (animationLoop):
function loadImage(sprite, handleComplete) {
  sprite.image = new Image();
  sprite.image.onload = function () {
    sprite.w = sprite.image.width;
    sprite.h = sprite.image.height;
    if (typeof handleComplete === 'function')
      handleComplete();
  };
  sprite.image.src = sprite.src;
}

function loadResources() {
  const resourcesToLoad = [];
  //cerco le risorse da caricare
  arena.sprites.forEach(sprite => {
    if (sprite.type === SpriteType.IMAGE) {
      resourcesToLoad.push(sprite);
    }
  });
  // le carico
  let i = resourcesToLoad.length;
  if (i === 0) {
    animationLoop();
  } else {
    resourcesToLoad.forEach(sprite => {
      new loadImage(sprite, function(){
        i--;
        if (i <= 0) {
          //attendo il caricamento di tutte le immagini
          animationLoop();
        }
      });
    });
  }
}
a questo punto il metodo init diventa:
function init() {
  createSprites();
}

init();
Il risultato è l'animazione a inizio pagina.

Vedi anche: