How to render images in OffscreenCanvas

Javascript is single threaded meaning everything runs on one thread, this thread is called the main thread. At times there's a lot running on the main thread which affects performance of our apps. To improve this performance, we want to reduce the load on the main thread and run some of these process elsewhere(another thread maybe), this is where workers come in. Web workers enable multithreading in javascript.

Note that web workers are different from service workers

How web workers work?

Web workers essentially exchange payloads with your main thread, so if your main thread wants to offload some tasks it will send the variables to your web worker which will process then and return your results to the main thread. All this run in parallel.

Data is sent by calling worker.postMessage(data) in between the threads. These threads receive and handle the data through 'message' listener.

For example

/*
*  sample.worker.js
*/

// The `message` event is fired in a web worker any time `worker.postMessage(data)` is called.
// `event.data` represents the data being passed into a worker via `worker.postMessage(<data>)`.
onmessage = e => {
   const data = e.data
   if(!NaN(data)) {
     const result = data ** 2
     postMessage(result)
   }
   console.log("Not Number")
   postMessage(NaN)
 }
}

// Alternatively we can also use 
// addEventListener('message', async e => { ... })
/*
*  main.js
*/

const MyWorker = new Worker('/workers/sample.worker.js')
MyWorker.postMessage(4)

MyWorker.onmessage = e => {
  document.querySelector('#output').innerHTML = e.data
}

Limitations of workers

  • You cannot access the DOM.
  • You cannot load images.
  • You cannot create canvas elements and draw to them from web workers.

What about OffscreenCanvas?

When the main thread is overloaded with complex computations, mostly animations on canvas, we want to move this to a worker. OffscreenCanvas detaches our canvas from the DOM and runs it in the worker.

Usage

/*
*  main.js
*/

const canvas = document.getElementById("canvas");
const offscreen = canvas.transferControlToOffscreen();

const worker = new Worker("/workers/sample.worker.js");
// The first arg is the payload, we can pass what we want to access on the worker
// The second arg is an array containing a transferrable which is our OffscreenCanvas
worker.postMessage({canvas: offscreen}, [offscreen]);
/*
*  sample.worker.js
*/

onmessage = e => {
  const canvas = evt.data.canvas;
  const cxt = canvas.getContext("2d");

  // your drawings
};

Rendering Images in OffscreenCanvas

  • Firstly, we'll pass the url of the image to the worker since we can't access it on the DOM
  • Create a Blob from the image url source
  • Create a bitmap image from the blob
  • And finally draw our BitmapImage onto the OffscreenCanvas

Example

/* Our HTML */
<body>
  <img data-src="/images.png">
</body>

Notice that it's data-src and not src but that's because we want it to hold that data and not render it on the DOM (If you want it to render on the DOM we can add the src attribute). We want it on the OffscreenCanvas.

/*
*  main.js
*/

const canvas = document.getElementById("canvas");
const offscreen = canvas.transferControlToOffscreen();

// Get url from data-src
const imageElements = document.querySelectorAll('img[data-src]')
const imageURL = imageElements[0].getAttribute('data-src')

const worker = new Worker("/workers/sample.worker.js");

// This time we also pass the image url
worker.postMessage({
  canvas: offscreen,
  imageURL
}, [offscreen]);
/*
*  sample.worker.js
*/

onmessage = async (e) => {
  const canvas = evt.data.canvas;
  const imageUrl = e.data.imageURL

  // Create Blob
  const response = await fetch(imageUrl)
  const imgBlob = await response.blob()

  // Create ImageBitmap
  img = await createImageBitmap(imgBlob)

  // Get image dimensions
  const width = img.width;
  const height = img.height;

  // Set canvas dimensions
  canvas.width = width;
  canvas.height = height;

  const cxt = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0, width, height);
};

That's all

Helpful resources