I couldn't always use pointer-events: none in my scenario, because I wanted both the overlay and the underlying element(s) to be clickable / selectable.
The DOM structure looked like this:
<div id="outerElement"> <div id="canvas-wrapper"> <canvas id="overlay"></canvas> </div> <!-- Omitted: element(s) behind canvas that should still be selectable --> </div>
(The outerElement, canvas-wrapper and canvas elements have the same size.)
To make the elements behind the canvas act normally (e.g. selectable, editable), I used the following code:
canvasWrapper.style.pointerEvents = 'none'; outerElement.addEventListener('mousedown', event => { const clickedOnElementInCanvas = yourCheck // TODO: check if the event *would* click a canvas element. if (!clickedOnElementInCanvas) { // if necessary, add logic to deselect your canvas elements ... wrapper.style.pointerEvents = 'none'; return true; } // Check if we emitted the event ourselves (avoid endless loop) if (event.isTrusted) { // Manually forward element to the canvas const mouseEvent = new MouseEvent(event.type, event); canvas.dispatchEvent(mouseEvent); mouseEvent.stopPropagation(); } return true; });
Some canvas objects also came with input fields, so I had to allow keyboard events, too. To do this, I had to update the pointerEvents property based on whether a canvas input field was currently focused or not:
onCanvasModified(canvas, () => { const inputFieldInCanvasActive = // TODO: Check if an input field of the canvas is active. wrapper.style.pointerEvents = inputFieldInCanvasActive ? 'auto' : 'none'; });