Capture mouse events from sibling or parent DOM element in PIXI.js
Asked Answered
P

5

11

I would like to capture mouse events on two layers: PIXI's canvas and and overlaying div. I have the following kind of HTML setup where div.overlay is above canvas.pixi:

<div class="parent">
  <canvas class="pixi"></canvas>
  <div class="overlay"></div>
</div>

PIXI interaction work fine when the canvas is on top but I am unable to capture any events when the canvas is overlaid with div.overlay.

I found setTargetElement which seem to let us define the DOM element for capture elements and I tried to use it like so:

const renderer = PIXI.autoDetectRenderer(...);
renderer.plugins.interaction.setTargetElement(document.querySelector('.overlay'));

Using this technique I am able to capture mousemove events but unfortunately click, mousedown, etc. do not work.

I've also tried to copy the original events captured on div.overlay and duplicate ans dispatch the events on canvas as shown below but that also doesn't do the trick.

document.querySelector('.overlay').addEventListener('mousedown', (e) => {
  const eventCopy = document.createEvent('MouseEvents');
  eventCopy.initMouseEvent(
    e.type, e.bubbles, e.cancelable, e.view,
    e.detail, e.pageX || e.layerX,
    e.pageY || e.layerY, e.clientX,
    e.clientY, e.ctrlKey, e.altKey, e.shiftKey,
    e.metaKey, e.button, e.relatedTarget
  );

  document.querySelector('.pixi').dispatchEvent(eventCopy);
});

Is there any way to capture mouse events on an overlaid DOM element and to pass the events to PIXI?

Why?

I would like to interact with PIXI elements while at the same time being able to utilize D3's zoom and brush functionality, which is currently being handled on the overlaying div.

Update & Code Example

I managed to forward events and all but click events are registered by PIXI. Click events can be manually triggered by re-firing pointerdown and pointerup events. Check out https://jsfiddle.net/v3chhhjk/1/

Pantomime answered 1/12, 2017 at 1:58 Comment(0)
P
1

I got it working for mouseover, mouseout, mousedown, mouseup, and click events by cloning the events and reconstructing click events from mousedown and mouseup events. All but click events work out of the box when cloning events from the event layer but somehow click events do not fire.

So given the following HTML structure:

<div id="parent">
  <canvas id="pixi"></canvas>
  <div id="overlay"></div>
</div>

I listen to a bunch of events like so:

const overlay = document.getElementById('overlay');

overlay.addEventListener('pointerdown', forwardDown);
overlay.addEventListener('pointerup', forwardUp);
overlay.addEventListener('pointermove', forward);
overlay.addEventListener('pointerover', forward);
overlay.addEventListener('pointerout', forward);

And then forwarding / cloning the events:

const canvas = document.getElementById('pixi');
let mousedown = false;

const clone = e => new e.constructor(e.type, e);
const forward = (e) => { canvas.dispatchEvent(clone(e)); };
const forwardDown = (e) => { mousedown = true; forward(e); };
const forwardUp = (e) => {
  forward(e);
  if (mousedown) console.log('It\'s a click!');
  mousedown = false;
};

The event can also be dispatched elsewhere do be recognized by D3 for example.

Demo: https://jsfiddle.net/v3chhhjk/3/

The top-left corner demonstrates how it works when directly catching events with PIXI. Top-right shows the issue when cloning events. Bottom-left shows a weird behavior where re-firing the same events actually makes PIXI recognize click events and finally, bottom right.

Pantomime answered 16/12, 2017 at 18:37 Comment(0)
C
4

One possible solution is to capture all the events at the parent and traverse it back to the child elements. Like this. This is done for click, same works for mousemove also.

function traverseEventToChild(e){
	if(e.type="click"){
  	const eventCopy = document.createEvent('MouseEvents');
  	eventCopy.initMouseEvent(
      e.type, e.bubbles, e.cancelable, e.view,
      e.detail, e.pageX || e.layerX,
      e.pageY || e.layerY, e.clientX,
      e.clientY, e.ctrlKey, e.altKey, e.shiftKey,
      e.metaKey, e.button, e.relatedTarget
  	);
    console.log('parent handler');
  	document.querySelector('.pixi').dispatchEvent(eventCopy);
  }
}

function piximousemove(e){
	console.log('pixi click');
  e.stopPropagation();
}

function overlaymousemove(e){
	console.log('overlay click');
}
document.querySelector('.pixi').onclick = piximousemove;
document.querySelector('.overlay').onclick = overlaymousemove;
<div class="parent" id='parent' onclick="traverseEventToChild(event);">
  <canvas class="pixi" id='canvas'>pixi</canvas>
  <div class="overlay" id='overlay'>overlay</div>
</div>
Constantia answered 14/12, 2017 at 7:49 Comment(3)
This works fine with plain JS and div elements but are you getting this working with PIXI.js too? I tried something similar but ended up having to track the click event myself using mousedown and mouseupPantomime
@FLekschas I haven't tried it with PIXI.js but if PIXI.js follows plain JS standards then the above should work fine unless PIXI.js blocks the event propagation in its handlers.Constantia
Well, I've tested it and it doesn't work that's why I asked how to get it working in PIXI.js. Thanks anyway and I've figured it out.Pantomime
C
2

For people visiting this page, but want PIXI to be the overlay, over a still interactive underlying layer. Then you can still use the example from @F Lekschas with a small modification.

Set pointer-events: none on the parent, then pointer-events: all on the underlay and then forward events from the underlay to the PIXI canvas, like in the example.

I was pulling my hair out over this one. I was running into all kind of problems forwarding events from the parent to the underlay, but forwarding events from the underlay to the PIXI canvas works like a charm.

Coparcener answered 28/10, 2021 at 10:13 Comment(0)
K
1

If you need to just pass events through the overlay - use css pointer-events: none;.
More details here: https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events

Kenrick answered 1/12, 2017 at 2:28 Comment(1)
Sorry I should have been more clear: I need to events to capturable on both layers: the overlay and PIXI. The CSS trick unfortunately doesn't help here.Pantomime
D
1

The problem is that the DOM is hierarchical. Here's an old issue about the exact same thing. It turns out, you can't trivially propagate events to siblings, only parents and children. I would suggest putting your canvas inside of the .overlay, then you can both capture it and catch it on the bubbling phase, depending if you want to handle those before or after the canvas.

I think the below should work as expected.

HTML:

<div id="parent">
    <div id="overlay">
        <canvas></canvas>
    </div>
</div>

Example JS:

(function() {
    let parent = document.getElementById('parent');
    let overlay = document.getElementById('overlay');
    let canvas = document.querySelector('canvas');

    parent.addEventListener('click', e => console.log('parent capture'), true);
    overlay.addEventListener('click', e => console.log('overlay capture'), true);
    canvas.addEventListener('click', e => console.log('canvas capture'), true);
    canvas.addEventListener('click', e => console.log('canvas bubble'), false);
    overlay.addEventListener('click', e => console.log('overlay bubble'), false);
    parent.addEventListener('click', e => console.log('parent bubble'), false);

})()
Diaghilev answered 14/12, 2017 at 14:50 Comment(1)
Unfortunately this is not an option. I also figured out I can forward events to siblings so that actually works fine. Check out the updated code example I provided.Pantomime
P
1

I got it working for mouseover, mouseout, mousedown, mouseup, and click events by cloning the events and reconstructing click events from mousedown and mouseup events. All but click events work out of the box when cloning events from the event layer but somehow click events do not fire.

So given the following HTML structure:

<div id="parent">
  <canvas id="pixi"></canvas>
  <div id="overlay"></div>
</div>

I listen to a bunch of events like so:

const overlay = document.getElementById('overlay');

overlay.addEventListener('pointerdown', forwardDown);
overlay.addEventListener('pointerup', forwardUp);
overlay.addEventListener('pointermove', forward);
overlay.addEventListener('pointerover', forward);
overlay.addEventListener('pointerout', forward);

And then forwarding / cloning the events:

const canvas = document.getElementById('pixi');
let mousedown = false;

const clone = e => new e.constructor(e.type, e);
const forward = (e) => { canvas.dispatchEvent(clone(e)); };
const forwardDown = (e) => { mousedown = true; forward(e); };
const forwardUp = (e) => {
  forward(e);
  if (mousedown) console.log('It\'s a click!');
  mousedown = false;
};

The event can also be dispatched elsewhere do be recognized by D3 for example.

Demo: https://jsfiddle.net/v3chhhjk/3/

The top-left corner demonstrates how it works when directly catching events with PIXI. Top-right shows the issue when cloning events. Bottom-left shows a weird behavior where re-firing the same events actually makes PIXI recognize click events and finally, bottom right.

Pantomime answered 16/12, 2017 at 18:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.