Migliorando l'animazione precedente, Come creare un animazione in JavaScript (2), si può affinare il codice creando degli oggetti che rappresentano le forme (sprite) gestite:
Se non vedi l'animazione clicca sul canvas o ricarica la pagina

Definisco un oggetto che identifica le forme:
//tipi di sprite gestiti
const SpriteType = {
  BACKGROUND: 'background',
  CIRCLE: 'circle',
  RECTANGLE: 'rectangle',
  TEXT: 'text',
};
poi i singoli sprite e un oggetto arena che rappresenta lo spazio dell'animazione:
//definisco un oggetto che rappresenta l'area da disegnare
function Arena(w, h) {
  this.w = w;
  this.h = h;
  this.sprites = [];  //sposto l'array 'sprites'
}

//creo l'arena
const arena = new Arena(canvas.width, canvas.height);

//aggiungo un metodo per 'aggiungere' gli sprites
Arena.prototype.addSprite = function (sprite) {
  this.sprites.push(sprite);
}

//definisco i tipi di oggetti da disegnare
function SpriteBackground(x, y, w, h, fillColor) {
  this.type = SpriteType.BACKGROUND;
  this.x = x; // x e y sempre riferiti all'angolo in alto a sinistra
  this.y = y;
  this.w = w;
  this.h = h;
  this.fillColor = fillColor == null ? null : fillColor;
  this.animationOn = false;
  this.alpha = 1;
}

function SpriteCircle(x, y, radius, color, fillColor, deltaX, deltaY) {
  this.type = SpriteType.CIRCLE;
  this.x = x;
  this.y = y;
  this.radius = radius;
  this.color = color == null ? null : color;
  this.fillColor = fillColor == null ? null : fillColor;
  this.deltaX = deltaX == null ? 0 : deltaX;
  this.deltaY = deltaY == null ? 0 : deltaY;
  this.animationOn = this.deltaX !== 0 || this.deltaY !== 0;
  this.startAngle = 0;
  this.endAngle = Math.PI * 2;
  this.alpha = 1;
}

function SpriteRect(x, y, w, h, color, fillColor, deltaX, deltaY) {
  this.type = SpriteType.RECTANGLE;
  this.x = x; // x e y sempre riferiti al centro dell'oggetto
  this.y = y;
  this.w = w;
  this.h = h;
  this.color = color == null ? null : color;
  this.fillColor = fillColor == null ? null : fillColor;
  this.deltaX = deltaX == null ? 0 : deltaX;
  this.deltaY = deltaY == null ? 0 : deltaY;
  this.animationOn = this.deltaX !== 0 || this.deltaY !== 0;
  this.alpha = 1;
}

function SpriteText(x, y, text, font, color, fillColor, textAlign, baseline, deltaX, deltaY) {
  this.type = SpriteType.TEXT;
  this.x = x;
  this.y = y;
  this.text = text;
  this.font = font == null ? '14px Arial' : font;
  this.color = color == null ? null : color;
  this.fillColor = fillColor == null ? null : fillColor;
  this.textAlign = textAlign == null ? 'left' : textAlign;
  this.baseline = baseline == null ? 'bottom' : baseline;
  this.deltaX = deltaX == null ? 0 : deltaX;
  this.deltaY = deltaY == null ? 0 : deltaY;
  this.animationOn = this.deltaX !== 0 || this.deltaY !== 0;
  this.alpha = 1;
}
ridefinisco i metodi updatePosition edraw:
function updatePosition() {
  arena.sprites.forEach(sprite => {
    if (sprite.animationOn === true) {
      // per ogni oggetto calcolo la nuova posizione
      sprite.x += sprite.deltaX;
      sprite.y += sprite.deltaY;
    }
  });
}

function draw() {
  //avendo gestito i type 'rectangle'
  //non devo più cancellare lo sfondo in modo esplicito
  arena.sprites.forEach(sprite => {
    ctx.globalAlpha = sprite.alpha;
    switch (sprite.type) {
      case SpriteType.BACKGROUND:
        ctx.beginPath();
        ctx.rect(sprite.x, sprite.y, sprite.w, sprite.h);
        ctx.fillStyle = sprite.fillColor; //colore di riempimento
        ctx.fill();
        break;

      case SpriteType.RECTANGLE:
        ctx.beginPath();
        const x = sprite.x - sprite.w / 2;
        const y = sprite.y - sprite.h / 2;
        ctx.rect(x, y, sprite.w, sprite.h);
        if (sprite.fillColor !== null) {
          ctx.fillStyle = sprite.fillColor; //colore di riempimento
          ctx.fill();
        }
        if (sprite.color !== null) {
          ctx.strokeStyle = sprite.color; // colore del bordo
          ctx.stroke();
        }
        break;

      case SpriteType.CIRCLE:
        ctx.beginPath();
        ctx.arc(sprite.x, sprite.y, sprite.radius, 0, 2 * Math.PI);
        if (sprite.fillColor !== null) {
          ctx.fillStyle = sprite.fillColor; //colore di riempimento
          ctx.fill();
        }
        if (sprite.color !== null) {
          ctx.strokeStyle = sprite.color; // colore del bordo
          ctx.stroke();
        }
        break;

      case SpriteType.TEXT:
        ctx.font = sprite.font;
        ctx.textAlign = sprite.textAlign;
        ctx.textBaseline = sprite.baseline;
        if (sprite.fillColor !== null) {
          ctx.fillStyle = sprite.fillColor; //colore di riempimento
          ctx.fillText(sprite.text, sprite.x, sprite.y);
        }
        if (sprite.color !== null) {
          ctx.strokeStyle = sprite.color; // colore del bordo
          ctx.strokeText(sprite.text, sprite.x, sprite.y);
        }
        break;

    }
  });
}
e aggiungo gli sprites con i nuovi metodi:
function createSprites() {
  arena.sprites.length=0;
  const cx = canvas.width / 2;
  const cy = canvas.height / 2;
  //aggiungo lo sfondo
  arena.addSprite(new SpriteBackground(0, 0, canvas.width, canvas.height, '#444'));
  //aggiungo un testo 
  const text1 = new SpriteText(cx, cy, 'Sgart.it', '150px Arial', '#ccc',null,  'center', 'middle');
  text1.alpha = .5;
  arena.addSprite(text1);
  //aggiungo gli altri oggetti da animare
  arena.addSprite(new SpriteCircle(50, canvas.height - 50, 35, '#ccc', 'red', 3, -2));
  // creo un testo sotto al rettangolo
  arena.addSprite(new SpriteText(100, 400, 'Testo di sfondo sotto al rettangolo giallo', '25px Arial', '#00f'));
  //definisco un oggetto trasparente
  arena.addSprite(new SpriteRect(50, 50, 40, 45, '#fff', 'yellow', 1, 2));
  //aggiungo un testo 
  const text2 = new SpriteText(200, 150, 'testo in primo piano', '70px Arial', null, '#f0f');
  text1.alpha = .5;
  arena.addSprite(text2);
  //l'ultimo sprite definito è quello più in alto, sopra a tutti
}
Il risultato è l'animazione a inizio pagina.