Search code examples
javascriptcanvasweb-workerpostmessage

JavaScript postMessage and multiple transferable objects


I'm trying to use the experimental Canvas feature - offscreenCanvas.

index.html

<html>
  <body>
    <canvas id="myCanvas" width="400" height="400"></canvas>
  </body>

  <script>
    const canvas = document.querySelector('#myCanvas');
    const offscreenCanvas = canvas.transferControlToOffscreen();

    const worker = new Worker('./worker.js');
    worker.postMessage({offscreenCanvas}, [offscreenCanvas]);

  </script>
</html>


worker.js

onmessage = e => {
  const ctx = e.data.offscreenCanvas.getContext('2d');

  ctx.fillRect(0 , 0, 100 , 100);
  ctx.commit();
}

Everything is working fine here, I draw simple rectangle on the canvas. Now - how to pass additional information with postMessage? For example - is it possible to send object with rectangle coordinates?

Like

const obj = {
  x: 0,
  y: 0,
}

I tried to use transferable objects by creating new Int8Array:

const bufferObj = new Int8Array([obj.x, obj.y]);

To be honest I don't know how to send it to worker and read it from the event object. I was thinking about

worker.postMessage(offscreenCanvas, bufferObj, [offscreenCanvas, bufferObj]);

But this throw an error Value at index 1 does not have a transferable type. Any idea how to solve this?


Solution

  • First, if you don't need to transfer the ownership of the object, but just send it to the worker, you can do that:

    const obj = {
      x: 0,
      y: 0,
    }
    
    worker.postMessage({offscreenCanvas, obj}, [offscreenCanvas]);
    

    And then just access from the worker to e.data.obj without issues. For simple object I would say it's not a major issue, since postMessage use Structured clone algorithm

    However, if you need optimization, and you pass a large amount of data – as an offscreen canvas – you want to transfer the object on the other side instead.

    In such case, your error was using a Typed Array that does not implement a Transferable interface. You have, instead, to pass the ArrayBuffer. For example:

    const buff = new Uint8Array([obj.x, obj.y]).buffer;
    
    worker.postMessage({offscreenCanvas, buff}, [offscreenCanvas, buff]);
    

    That should works.