Question

How to pass an image sequence for a canvas in React?

I have a list of 150 images called 0001.jpg, 0002.jpg ... 0150.jpg.

This is what I'm doing so far (only included the relevant code):

import React, { useRef, useEffect, useState } from 'react';

function getCurrentFrame(index) {
  return `../${index.toString().padStart(4, "0")}.jpg`;
}

const ImageCanvas = ({ scrollHeight, numFrames, width, height }) => {
  const canvasRef = useRef(null);
  const [images, setImages] = useState([]);
  const [frameIndex, setFrameIndex] = useState(0);

  // Step 1: Load images
  function preloadImages() {
    for (let i = 1; i <= numFrames; i++) {
      const img = new Image();
      const imgSrc = getCurrentFrame(i);
      img.src = imgSrc;
      setImages((prevImages) => [...prevImages, img]);
    }
  }
    const render = () => {
      console.log(images[frameIndex]);
      context.drawImage(images[frameIndex], 0, 0);
      requestId = requestAnimationFrame(render);
    };

    render();

I get this error when it tries to drawImage:

"Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state. at render"

console.log(images[frameIndex]); prints the correct element three to five times before showing the error:

<img src="../0001.jpg"> 

I'm certain the image source is correct because I set "img.src" in preloadImages() to the same src "../0001.jpg" and it shows the image.

Any idea what could be going wrong? Is there a better way to do it?

 2  33  2
1 Jan 1970

Solution

 1

Hard to say without the code on how you grab the context etc...

I use to work a lot with Canvas. Here I would load in memory all the images beforehand and do something like this recursive animation loop:

const framesUrls = ["https://iili.io/dxVWkZB.jpg","https://iili.io/dxVW8CP.jpg","https://iili.io/dxVhjzQ.jpg","https://iili.io/dxVhNLB.jpg","https://iili.io/dxVheqP.jpg","https://iili.io/dxVhk11.jpg","https://iili.io/dxVhvrF.jpg","https://iili.io/dxVhSdg.jpg","https://iili.io/dxVhU7a.jpg","https://iili.io/dxVhgkJ.jpg","https://iili.io/dxVhrmv.jpg","https://iili.io/dxVh6IR.jpg","https://iili.io/dxVhPXp.jpg","https://iili.io/dxVhiLN.jpg","https://iili.io/dxVhLBI.jpg","https://iili.io/dxVhQ1t.jpg","https://iili.io/dxVhZrX.jpg","https://iili.io/dxVhD2n.jpg","https://iili.io/dxVhb7s.jpg","https://iili.io/dxVhmkG.jpg","https://iili.io/dxVhppf.jpg","https://iili.io/dxVj9I4.jpg","https://iili.io/dxVjHhl.jpg","https://iili.io/dxVjJQ2.jpg","https://iili.io/dxVj2BS.jpg","https://iili.io/dxVj3E7.jpg","https://iili.io/dxVjF49.jpg","https://iili.io/dxVjf2e.jpg","https://iili.io/dxVjqYu.jpg","https://iili.io/dxVjBkb.jpg","https://iili.io/dxVjCpj.jpg","https://iili.io/dxVjoTx.jpg","https://iili.io/dxVjxhQ.jpg","https://iili.io/dxVjzQV.jpg","https://iili.io/dxVjTCB.jpg","https://iili.io/dxVjuEP.jpg","https://iili.io/dxVjA41.jpg","https://iili.io/dxVj53F.jpg","https://iili.io/dxVj7Yg.jpg","https://iili.io/dxVjYva.jpg","https://iili.io/dxVjayJ.jpg","https://iili.io/dxVjlTv.jpg","https://iili.io/dxVj0jR.jpg","https://iili.io/dxVXTwQ.jpg","https://iili.io/dxVXutV.jpg","https://iili.io/dxVXRoB.jpg","https://iili.io/dxVX5MP.jpg","https://iili.io/dxVX7P1.jpg","https://iili.io/dxVXaKF.jpg","https://iili.io/dxVXccg.jpg","https://iili.io/dxVXlSa.jpg","https://iili.io/dxVX1HJ.jpg","https://iili.io/dxVXEAv.jpg","https://iili.io/dxVXGNR.jpg","https://iili.io/dxVXMtp.jpg","https://iili.io/dxVXWoN.jpg","https://iili.io/dxVXXVI.jpg","https://iili.io/dxVXhPt.jpg","https://iili.io/dxVXwKX.jpg","https://iili.io/dxVXNln.jpg","https://iili.io/dxVXOSs.jpg","https://iili.io/dxVXkHG.jpg","https://iili.io/dxVXvRf.jpg","https://iili.io/dxVX8N4.jpg","https://iili.io/dxVXSDl.jpg","https://iili.io/dxVXgx2.jpg","https://iili.io/dxVXrVS.jpg","https://iili.io/dxVX4i7.jpg","https://iili.io/dxVXPf9.jpg","https://iili.io/dxVXile.jpg","https://iili.io/dxVXsUu.jpg","https://iili.io/dxVXQHb.jpg","https://iili.io/dxVXZRj.jpg","https://iili.io/dxVXtOx.jpg","https://iili.io/dxVXDDQ.jpg","https://iili.io/dxVXmxV.jpg","https://iili.io/dxVXpWB.jpg","https://iili.io/dxVXyiP.jpg","https://iili.io/dxVhHf1.jpg","https://iili.io/dxVhJ0F.jpg","https://iili.io/dxVhdUg.jpg","https://iili.io/dxVh3Ja.jpg","https://iili.io/dxVhF5J.jpg","https://iili.io/dxVhKOv.jpg","https://iili.io/dxVhfbR.jpg","https://iili.io/dxVhCWN.jpg","https://iili.io/dxVhnsI.jpg","https://iili.io/dxVhxft.jpg","https://iili.io/dxVhz0X.jpg","https://iili.io/dxVhIgn.jpg","https://iili.io/dxVhuJs.jpg","https://iili.io/dxVhA5G.jpg","https://iili.io/dxVhRef.jpg","https://iili.io/dxVh5b4.jpg","https://iili.io/dxVhYzl.jpg","https://iili.io/dxVhaX2.jpg","https://iili.io/dxVhcsS.jpg","https://iili.io/dxVh0q7.jpg","https://iili.io/dxVh119.jpg","https://iili.io/dxVhEge.jpg","https://iili.io/dxVhMdu.jpg","https://iili.io/dxVhV5b.jpg","https://iili.io/dxVhWej.jpg","https://iili.io/dxVhXmx.jpg","https://iili.io/dxVWSG1.jpg","https://iili.io/dxVWr3g.jpg","https://iili.io/dxVW4aa.jpg","https://iili.io/dxVW68J.jpg","https://iili.io/dxVWPyv.jpg","https://iili.io/dxVWsuR.jpg","https://iili.io/dxVWLjp.jpg","https://iili.io/dxVWQZN.jpg","https://iili.io/dxVWtnI.jpg","https://iili.io/dxVWDGt.jpg","https://iili.io/dxVWb6X.jpg","https://iili.io/dxVWpFn.jpg","https://iili.io/dxVWyas.jpg","https://iili.io/dxVX98G.jpg","https://iili.io/dxVXJ9f.jpg","https://iili.io/dxVXdu4.jpg","https://iili.io/dxVX2wl.jpg","https://iili.io/dxVX3t2.jpg","https://iili.io/dxVXKnS.jpg","https://iili.io/dxVXfM7.jpg","https://iili.io/dxVXqP9.jpg","https://iili.io/dxVXCFe.jpg","https://iili.io/dxVXncu.jpg","https://iili.io/dxVXo8b.jpg","https://iili.io/dxVXz9j.jpg","https://iili.io/dxVXIAx.jpg","https://iili.io/dxVWMG9.jpg","https://iili.io/dxVWGC7.jpg","https://iili.io/dxVW0j2.jpg","https://iili.io/dxVW1QS.jpg","https://iili.io/dxVWV4e.jpg","https://iili.io/dxVWX3u.jpg","https://iili.io/dxVWhYb.jpg","https://iili.io/dxVWjvj.jpg","https://iili.io/dxVWwyx.jpg","https://iili.io/dxVWOTQ.jpg","https://iili.io/dxVWejV.jpg"];

// Create array of Image elements
const images = framesUrls.map(frameUrl => {
  const img = new Image();
  img.src = frameUrl;
  img.onload = () => {
    console.log("Successfully loaded frame url: ", frameUrl);
    registerLoadingProgress();
  }
  img.onerror = () => {
    console.error("Failed to load frame url: ", frameUrl);
    registerLoadingProgress();
  };
  return img;
});

let loadedCount = 0;

function registerLoadingProgress() {
  loadedCount++;
  if (loadedCount === framesUrls.length) {
    console.clear();
    console.log("Loading complete - Start animation loop");
    drawImages();
  }
}

const frameRate = 30; // 30 frames per second
const ctx = document.getElementById("canvas").getContext("2d");

function drawImages(frameNumber = 0, lastFrame = Date.now()) {
  if (images[frameNumber]) {
    ctx.drawImage(images[frameNumber], 0, 0, 320, 180);
    if (Date.now() - lastFrame >= 1000 / frameRate) {
      frameNumber++;
      lastFrame = Date.now();
    }
    requestAnimationFrame(() => drawImages(frameNumber, lastFrame));
  }
}
<html>
  <head>
  </head>
  <body>
    <canvas id="canvas" width="320" height="180"></canvas>
  </body>
</html>

2024-07-24
Thomas Zimmermann