ShukantPal / pixi-essentials

The best goodies for performant, enterprise-level applications built on PixiJS
https://api.pixijs.io
MIT License
275 stars 36 forks source link

Can't get pointer events to work #50

Open Friksel opened 2 years ago

Friksel commented 2 years ago

Whatever I try, I can't get pointer events to work when adding an SVGScene to the stage. Whatever else I add to the stage works interactively fine and also the stage itself fires events just fine, but for some reason not the SVG Scene. Interactive of the scene is set to true tho (also tried buttonMode just to be sure)

I tried several event types, like pointerup, tap and click. Also tried both .on() and .addListener() on both the SVGScene object (which is extended from DisplayObject) as well as the svgScene.root just to be sure.

Also created a complete fresh project with only pixi in it, just a stage and only the SVGScene added to it. But nothing seems to work.

According to the docs the events should be supported, so I don't get this.

Is there anything we should know, other than settings interactive to true, to make interactivity with pointers work?

ShukantPal commented 2 years ago

Thanks for reporting this — can you upload the minimal repro you made onto JSFiddle / Replit. I wonder if it has to do with the SVG image you’re using.

Sent from my iPhone

Sent from my iPhone On Feb 20, 2022, at 9:09 AM, Friksel @.***> wrote:



Whatever I try, I can't get pointer events to work when adding an SVGScene to the stage. Whatever else I add to the stage works interactively fine and also the stage itself fires events just fine, but for some reason not the SVG Scene. Interactive of the scene is set to true tho (also tried buttonMode just to be sure)

I tried several event types, like pointerup, tap and click. Also tried both .on() and .addListener() on both the SVGScene object (which is extended from DisplayObject) as well as the svgScene.root just to be sure.

Also created a complete fresh project with only pixi in it, just a stage and only the SVGScene added to it. But nothing seems to work.

According to the docs the events should be supported, so I don't get this.

Is there anything we should know, other than settings interactive to true, to make interactivity with pointers work?

— Reply to this email directly, view it on GitHubhttps://github.com/ShukantPal/pixi-essentials/issues/50, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AFLJDB7NPP3XZJB3EKMP2CLU4DYZPANCNFSM5O4OX72A. You are receiving this because you are subscribed to this thread.Message ID: @.***>

Friksel commented 2 years ago

Hi @ShukantPal , thanks for your quick response. I've just created a demo here using the svg you use in the example on the readme: https://codepen.io/Friksel/pen/MWOVGQL

In the demo you see two objects in the stage Container: a regular Pixi Graphics object and an SVGScene object. Currently both objects have interactive set to true. When you open the console you see there are issues when hovering over the stage. These issues are caused by the SVGScene's interactive = true. When setting interactive on the svgScene to false the error messages are gone. This is not the case with the graphics object; when interactive is set to true there it just works out of the box and the click gets fired when clicking on the object.

Locally I don't see these error messages in the console tho. Locally the interactivity just doesn't work on the scene, while it does on other pixi objects. But I didn't use your svg, but a much simpler svg here (only a rectangle). It looks like the same issue, but with a slightly different setup, so looks to me like a demo that shows the issue tho, especially because it's now shown with the exact same svg you're using in the example.

Hope this helps. If I'm missing something I'm all ears.

Thanks in advance!

Friksel commented 2 years ago

Hi @ShukantPal , it's over a week ago I posted a demo project by your request, but didn't hear from you since.

We need pointer events to work on the svgScene for a project currently working on for a while now. Without even basic pointer events in the global scene (but we also need events on its children) there's basically a problem. I would think that if you saw the demo and there's no issue in the lib you would let me know, but now you're not reacting it seems like there really is an issue in the lib. If that's the case, please let me know.

Any chance you could take a look at the demo and help me on how to make pointer events work and/or fix the issue if that's the case?

Thanks in advance!

ShukantPal commented 2 years ago

Hi @Friksel , I can try to look at this this weekend.

Friksel commented 2 years ago

@ShukantPal Thanks! Very much appreciated!

ShukantPal commented 2 years ago

Hi @Friksel,

It's been a while since I worked on @pixi-essentials/svg; after a bit of re-reading, I've figured it out. SVGScene is not designed to be interactive out-of-the-box. You are supposed to override the containsPoint method to make it so.

SVGScene has it's own internal scene graph, which maybe extremely complicated and expensive to hit test. I'd recommend you treat the SVG image as a "rectangle image" and make the hit-test just check if the point is in "its bounds". The alternative is to recursively go through the internal scene graph and find a node that passes hit-testing.

In the following example, the SVGScene will respond to UI events anywhere in it's "view box" (defined by the <svg /> element). Please see this modified pen for an example.

import { Point } from '@pixi/math';
import { SVGScene } from '@pixi-essentials/svg';

// Temporary point to store result in.
const tempPoint = new Point();

class extends SVGScene {
    constructor(svg: SVGSVGElement) {
        super(svg);

        // Some mixin not working with ESM. This is optional if it works without it (doesn't on CodePen)
        this.trackedPointers = {};
    }

    containsPoint(point: Point): boolean {
        this.worldTransform.applyInverse(point, tempPoint);

        // Assuming viewBox x, y is 0
        return tempPoint.x > 0 && tempPoint.y > 0
            && tempPoint.x < this.content.viewBox.baseVal.width
            && tempPoint.y < this.content.viewBox.baseVal.height;
    }
}
ShukantPal commented 2 years ago

Let me know if you have any questions or suggestions on this!

Friksel commented 2 years ago

Hi @ShukantPal , Thanks for diving into it. I am very surprised this isn't already build in. That's a huge setback. Without being able to use the SVGScene with interactivity that enormously limits the things we can actually use the SVGScene for in real life applications. That's unfortunate.

I am now trying to extend the SVGScene class tho hoping to add this and try to implement your example. But I'm already bumping into all kinds of walls making this possible.

Starting at the beginning: the from() function to load the svg and return the scene-class needs to be overriden already, because the original class don't return the new overriden class, but a SVGScene object instead, so immediately after loading everything we added to our own extended class will never be available like this.

So I tried to override that from() method too (which doesn't feel right, as we are already creating all kinds of unnecesary duplicate code, making everything larger), just to find out that you're using all kinds of things from outside the class that's not available in our project and is not exported. Things like _SVG_DOCUMENT_CACHE and the _load() function. This makes it hard to say the least to actually override the class that shouldn't even needed to be changed.

Let's say I do manage to get that from() method extended (where it looks like I pretty much have to copy a lot of code making the output larger and having duplicate code that wille never get updated by an update on the module), than we're not there yet. And then the real fun only starts: adding all the things to have the interactivity working on the scene, its children, and everything recursively. And all that with several pointer event types and it should also work when used within other containers which can zoom in and out etc. Like the pixi-viewport we're using in this project.

I keep trying, because we're already working long time on a project which uses this and I would've never thought interactivity not working. But I could use all the help with this. It would help if the original class would at least have everything available for the extended class and the SVGScene object would never be returned hardcoded. If it's even possible to avoid that, I'm not sure (perhaps something like a 'self' should be possible?).

Let me know if you have any questions or suggestions on this!

Thanks. To be really honest my best and only suggestion would really be: please add interactivity! We can't live without really!

ShukantPal commented 2 years ago

Hey, I see the problem with extending. You can add in the method too instead since it's not defined:

SVGScene.prototype.containsPoint = function(point) { ... }
ShukantPal commented 2 years ago

If that ↑ doesn't work, I'll add interactivity :)

Friksel commented 2 years ago

Hey, I see the problem with extending. You can add in the method too instead since it's not defined:

Can't get that to work here; the prototype doesn't have the needed worldTransform as it doesn't seem to be (and cannot be?) static. Also, everything else is ES6++ here in a Webpack environment using Babel.

If that ↑ doesn't work, I'll add interactivity :)

Thanks a lot! Guess that time is now! ;)

ShukantPal commented 2 years ago
Screen Shot 2022-03-06 at 11 39 58 AM

Hmm I updated the pen with the prototype modification. The prototype isn't supposed to have the worldTransform - it is the instantiated object / SVGScene that has it.

Make sure you're not using arrow functions as they hard set the this.

I'll add containsPoint later tonight.

ShukantPal commented 2 years ago

ignore the trackedPointers tho

Friksel commented 2 years ago

@ShukantPal Thanks!

ShukantPal commented 2 years ago

@Friksel Did it work?

ShukantPal commented 2 years ago

@Friksel Would love to know what you guys are working on!

Friksel commented 2 years ago

@Friksel Did it work?

@ShukantPal I tested it without the arrow function and indeed this is now available inside the containsPoint function. Nice to know. The function works also when the SVGScene is inside another pixi container and I've tested it within pixi-viewport with success.

Now the containsPoint is added I've also tested other pointerevents and they seem to work on the scene as a whole: When setting interactive on the scene to true and use addListener to catch pointermove-events they are being fired when hovering over the scene. They also have e.data.global location values. Next to this I've tested and see coming in the 'pointerdown', 'pointerup', 'click' events. The wheel event isn't being fired here on the scene, but that's most probably because it's been captured by the wrapping pixi-viewport that handles the wheel.

I'll add containsPoint later tonight.

So it looks like adding the containsPoint fixes the interactivity for the SVGScene as a unit! Very nice. I see you closed the issue, but I don't see the change to the lib yet, so I might be missing something? The workaround is at place here now, so no hurry, but looking forward to get the new lib with the change!

This works fine for capturing movements in the SVGScene as a unit, but I'm failing to see how we can add click events to the children. I've tried it with code below, but am not sure if this is supposed to work like this. Am I missing something? How can we capture events on childelements to add events to graphic nodes as regions?

const child = this.map.root.children[0];
child.addListener('click', () => {
      console.log('click on child')
});

@Friksel Would love to know what you guys are working on!

It's an interactive illustration which can be dragged and zoomed users click on to get information on regions.

I'd say the containsPoint can be added! Just a sidenote (I'm sure you thought of this, but just in case): SVGs don't need to have a viewBox attribute, so not sure if this also works on those SVGs. For us that's no problem tho, as we're always working with viewBox-es and perhaps that's even a requirement by your lib? Just wanted to throw that out there.

Thanks again for your help!

ShukantPal commented 2 years ago

So the child problem is kind of why I didn't want to add a "default" way of implementing interactivity. Although @pixi-essentials/svg does provide a scene graph, it's a "disconnected" internal scene. The scene graph is an "implementation detail". Usually, if you are rendering an arbitrary SVG file — this won't be an issue (pun intended). That does means the this.root is not a child of SVGScene, and it's protected from the outside.

The reason behind this is to give you performance optimization out of the box:

Back to adding interactivity, there are a couple of ways to go about this:

  1. Since the "root" node is disconnected from the SVGScene, you could treat the SVGScene as a factory:
import { SVGScene } from '@pixi-essentials/svg';

const stage;
const scene = new SVGScene(svgsvgElement);

stage.addChild(scene['root']);

You loose the performance optimizations described earlier, but they might not matter if you need hit testing inside the scene already.

  1. Create an interaction bridge.

You can use PixiJS' TreeSearch to do hit testing for you on the "root" of inside all SVGScene events.

// TODO: Convert to factory format and reuse code for other events

const scene = new SVGScene(el);
const treeSearch = new TreeSearch();

scene.on('pointerdown', (e) => {
  // Note: Use a different event object
  treeSearch.hitTest(..., (nextEvent) => {
    nextEvent.target?.emit('pointerdown', nextEvent);
  });
);
});

The new EventSystem has better support for this. Here, you'd have to check if the target object changed between pointerdown and pointerup before emitting a click event.

With this method, you could use a faster hit testing algorithm if you know parts of the scene you don't want to search through or if you want to use something like a spatial hash cause the SVG tree is huge.

Friksel commented 2 years ago

@ShukantPal Thanks, I didn't have the time to test this yet, but I will. Does this mean you're not adding containsPoint like you would before?

ShukantPal commented 2 years ago

Yeah, since the prototype modification worked that doesn't seem necessary. I can add it to the documentation though.

Friksel commented 2 years ago

Yeah, since the prototype modification worked that doesn't seem necessary. I can add it to the documentation though.

It's your lib, so you decide, but I totally don't get this decision. Why wouldn't you just add it to fix the interactive not working/being broke? Right now all pointer events throw issues when using the way people are used to use pixi and displayobjects are supposed to work. While by only adding 2 lines of code you could avoid that and make interactive work on the scene and fix this. And prevent all kinds of confusion and us needing to do this strange workaround each time we use SVGScene on a project by some prototype function while working completely in ES6+...

gnoyixiang commented 6 months ago

Hey guys just wanted to check if there are any updates to this. We are looking to use this for adding interactivity on the sv elements, while keeping the performance optimisation mentioned.

ShukantPal commented 6 months ago

I haven’t had the time to make a fix. I’m accepting PRs though. I think adding a containsPoint method to your SVGScene as described should make basic interaction work.