It's a hidden canvas..but just how is it hidden?

See: Canvas Picking With A Hidden Canvas / Chris Camargo / Observable. I understand just about everything in this example, except for how the hidden canvas is actually being hidden! Can someone please point out to me where/how this is done?

The canvas is simply not attached to the DOM.

Creating multiple detached canvas elements is also a good way to emulate layers, e.g. to cache expensive drawing operations. To give an example:

{
  const width = 200, height = 200;
  const output = DOM.context2d(width, height);
  const layer1 = DOM.context2d(width, height);
  const layer2 = DOM.context2d(width, height);
  layer2.fillStyle = 'red';
  layer2.fillRect(.4 * width, .4 * height, .5 * width, .5 * height);
  
  const update = () => {
    // Update layer1
    layer1.clearRect(0, 0, width, height);
    layer1.fillStyle = `hsl(${(Date.now() / 10) % 360}, 100%, 50%)`;
    layer1.fillRect(.1 * width, .1 * height, .5 * width, .5 * height);
    
    // Clear the output canvas
    output.fillStyle = 'black';
    output.fillRect(0, 0, width, height);

    // Observable's DOM.context() automatically applies a transform
    // to scale the canvas according to the current devicePixelRatio.
    // We need to temporarily reset it before calling drawImage().
    output.save();
    output.resetTransform();
    
    // Draw the layers
    output.drawImage(layer1.canvas, 0, 0);
    output.drawImage(layer2.canvas, 0, 0);
    
    output.restore();
  }

  while(true) {
    update();
    yield output.canvas;
  }
  
}

Thanks @mootari, this makes some more sense now. I hadn’t even noticed that the hiddenContext was not even added.

I’m actually trying to add a hidden canvas to this notebook Countries Pan & Zoom, with neighbour colouring / stuwilmur / Observable to be used in the typical pattern to create a hidden picker layer (i.e. colour each country uniquely in the hidden context, then lookup the pixel colour to country mapping). I’m not too sure how to do this in my chart {} block but will have a go.

Update: I tried to implement this in my notebook by adding a second context to the chart block, but without success. Do I have to somehow have toe return both hidden and visible contexts? (I added a mouse move listener to the hidden one to check if it’s there)

The hidden context is only useful for its rendering capabilities and to call getImageData() on it. Attaching event handlers to its canvas does nothing, since it can’t be interacted with in any way - remember, it’s not part of the DOM.

What you want to do is:

  1. listen for mouse events on your attached canvas
  2. translate the mouse offset to a pixel offset
  3. fetch image data for a 1x1 pixel wide area around the pixel offset that you determined
  4. read the RGB value from imageData.data to identify the hovered object

Note that dealing with the antialiasing is not trivial. You may want to consider using a canvas library that already supports object picking, e.g. fabric.js or Konva.js.


A little background on the antialiasing problem: 2D canvas uses a technique called “premultiplied alpha”, which essentially means that a color’s transparency gets applied to its RGB values before drawing the color. This makes drawing with semi-transparent colors inherently unreliable if you want to fetch precise RGB values otherwise.

Thank you for helping to improve my understanding.

My issue is that the RGB colours are not unique, so I will have to change my colour mapping to something that is.