An issue with event listeners in Vue 3 and Pixi.js arises when working with DisplayObjects that are either created as properties of classes or inherited from parent classes

Utilizing PIXI js in conjunction with Vue 3 (see code snippet below) Due to the consistent pattern of most graphics with varying behaviors and properties, we opted for an OOP approach with TypeScript to prevent code duplication. However, following this approach often results in errors such as

cannot find propagation path to disconnected target

More information is provided in comments within the code

Before diving in, here's a brief overview

When the DisplayObject is instantiated as a class property or overridden from a parent class, event listeners do not function correctly, resulting in the

cannot find propagation path to disconnected target
error

Additionally, modifying the properties of the display object does not trigger a rerender

<script setup lang="ts">
const main = ref<HTMLElement | null>(null)
  
let visualisationEngine: VisualisationEngine

onMounted(() => {
  if (main.value) {
    visualisationEngine = new VisualisationEngine(main.value!!, window)
    visualisationEngine.init()
  }
})
</script>

VisualisationEngine.ts

import { Application, Graphics } from 'pixi.js'
import '@pixi/unsafe-eval' // used for an electron app 

import { onEscKeyDown, onPointerDown, onPointerMove, onPointerUp } from '@/visualisation_engine/eventHandlers'
import { Viewport } from 'pixi-viewport'

interface GlobalThis {
  __PIXI_APP__: Application<HTMLCanvasElement>
}

export class VisualisationEngine {
  app: Application<HTMLCanvasElement>
  elem: HTMLElement
  window: Window
  viewport: Viewport

  constructor(elem: HTMLElement, window: Window) {
    this.window = window

    this.app = new Application<HTMLCanvasElement>({
      antialias: true,
      autoDensity: true,
      resizeTo: elem,
      hello: true
    })

    this.viewport = new Viewport({
      ...
    })

    this.elem = elem

    ;(globalThis as any as GlobalThis).__PIXI_APP__ = this.app //for debugging w/ pixi devtools
  }

  init() {
    this.render()
    const background = this.drawBackground()
    this.startEventHandlers(background)
  }

  render() {
    this.elem.appendChild(this.app.view)
    this.app.stage.addChild(this.viewport)
  }

  drawBackground() {
    const background = new Graphics()
    ...

    this.viewport.addChild(background)

    return background
  }

  startEventHandlers(graphic: Graphics) {
    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        onEscKeyDown()
      }
    })

    graphic
      .on('pointerdown', (event) => {
        onPointerDown(event, (elem) => this.viewport.addChild(elem))
      })
      .on('pointermove', (event) => {
        onPointerMove(event, (elem) => this.viewport.addChild(elem))
      })
      .on('pointerup', (event) => onPointerUp(event))
  }
}

eventListeners.ts

export const onPointerDown = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => {
  const mainStore = useMainStore()

  ...
}

export const onPointerUp = (event: FederatedPointerEvent) => {
  const mainStore = useMainStore()

  ...
}

export const onPointerMove = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => {
  ...
}

BaseElement.ts

import { defineStore } from 'pinia'
import { Container, Graphics } from 'pixi.js'
import { useMainStore } from '@/store/main'
import { ref } from 'vue'

export abstract class BaseElement extends Container {
  useStore
  nodes: number[]
  abstract readonly type: string

  ...

  protected constructor(x: number = 0, y: number = 0, name: string, nodes: number[]) {
    ...
  }

  abstract draw(): Graphics

  initializeEventListeners(graphic: Graphics) {
    ...
  }

  move(x: number, y: number) {
    ...
  }
}

Sample element

import { BaseElement } from './BaseElement'
import { Graphics, Point } from 'pixi.js'

export class Resistor extends BaseElement {
  override type = 'Resistor'

  constructor(x: number, y: number, nodes: [number, number], name: string = 'R', resistance: number) {
    super(x, y, name, nodes)
  }

  override draw() {
    ...
  }
}

How are the elements added to the canvas

The user selects from various elements, and upon selection, a new object is created with the chosen element and added to the global pinia store instance like

mainStore.setElementToAdd(new Resistor(...))

setElementToAdd(element: BaseElement) {
...
}

Answer №1

After a couple of weeks, it became apparent that the object had been garbage collected right after its creation, and pinia was only keeping proxies to the object rather than the actual object itself. (This is just a theory)

As a result, we decided to refactor the VisualisationEngine class to be a singleton and store all pixijs objects within this class

In the end, it seems that vue's reactivity system doesn't work well in this scenario

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring cross-browser compatibility with the use of CSS3 and JavaScript

Starting a new project to create a fresh website. It seems like many people are leaning towards CSS3 and AJAX, neglecting browsers without JavaScript support. They resort to workarounds like enabling CSS3 through JavaScript in older browsers. Is this the ...

Using the "this" keyword to set a timer in a JavaScript class with setTimeout()

Implementing setTimeout() inside a class function in JavaScript is my current goal. The purpose of setTimeout() is to trigger another method within the same Class, so I have written the function as window.setTimeout("this.anotherMethod", 4000). However, th ...

Removing spaces within brackets on dynamic properties for objects can be achieved by utilizing various methods such as

I've encountered an issue with my code that involves getting spaces within square brackets for the dynamic properties of an object. Even after searching through Code Style/Typescript/Spaces, I couldn't find any settings to adjust this. Could thes ...

The type 'Navigator' does not have the property 'userAgentData' in its definition

Since I'm looking to minimize the information provided by navigator.userAgent, I decided to migrate to User-Agent Client Hints. However, I've encountered an error while attempting to do so: https://i.stack.imgur.com/lgIl7.png Could someone plea ...

Is it possible to reverse the use of JQuery's .each() function without any additional plugins or modifications?

Similar Question: Reversing JQuery .each() Is there a better approach to using .each() in reverse? Currently, I am implementing it like this: var temp = []; $("#nav a").each(function() { temp.push($(this)); }); temp.reverse(); for(var i = 0; i ...

Error Alert: Request missing connection details while trying to connect to Sql server via express.js

I am currently utilizing the most recent versions of node, express, and mssql module. My objective is to establish a connection with the local instance of SQL Server 2014 through express.js. Following the guidelines provided in the official documentation, ...

What is the best approach to structure a React project in which a component's rendering changes with each instance?

I am facing a challenge with rendering a component that relies on multiple AJAX calls to complete. These AJAX responses return different data each time, and I want to implement a button that will trigger the re-rendering of this random component every time ...

What is the method for initializing fancybox with the precise image index?

My current usage of fancybox involves the following code snippet: $("a[rel=Fancy" + UserId + "].Items").fancybox({ 'autoscale': 'false', 'cyclic': true, 'transitionIn': 'elastic', & ...

Utilizing AngularJS: Executing directives manually

As a newcomer to AngularJS, I am facing a challenge that requires creating a 3-step workflow: The initial step involves calling a web service that provides a list of strings like ["apple", "banana", "orange"]. Upon receiving this response, I must encap ...

Vue Issue - Unable to resolve 'https' dependency when importing library

Currently, I am working on a Vue project and attempting to utilize an npm package for interfacing with the retroachievements.org API in order to retrieve data. However, I have encountered an error during this process. Below is a detailed account of how I c ...

"Converting an array within a single object into an object: step-by-step

After retrieving a single row of record from MySQL using Node.js and Express.js, I found that the result was returned as an array inside an object. Here is the output of the result: [ { "user_id": 1, "name": "Aravinth", "email ...

Troubleshooting issues with the 'date' input type feature on mobile devices

When I use the following code: <input type='month' ng-readonly='vm.isReadonly' required min="{{vm.threeMonthsAgo}}" max='{{vm.oneMonthAhead}}'/> I am experiencing some difficulties on mobile devices that do not occur o ...

Having trouble deleting JavaScript object properties within a loop?

Struggling to comprehend the behavior of this particular piece of javascript code. const devices = searchResult.results.forEach(device => { const temp = Object.keys(device.fields); for(var property in temp) { if(device.fields.hasOwnPro ...

Encountered an issue in Typescript with error TS2554: Was expecting 0 arguments but received 1 when implementing useReducer and useContext in React

I've encountered several errors with my useReducers and useContext in my project. One specific error (TS2554) that I keep running into is related to the AuthReducer functionality. I'm facing the same issue with each Action dispatch. I've tri ...

How can I modify the date format using the Google Ajax Feed API?

Currently, I am utilizing Google's AJAX Feed API to pull data from an RSS feed. However, I am curious about how to modify the date format. The current format fed by "datePublished" appears as follows: Sun, 29 Jan 2012 23:37:38 -0800 Is there a way t ...

Guide to dynamically inserting an audio file into a div with jQuery

I am looking to dynamically insert an audio file. Below are the declared tags: <div> <audio id="myaudio"> </audio> </div> Now, I am trying to add the source dynamically. Can anyone help me with how to add it to the div or audi ...

The functionality of the Toastr "options" seems to be malfunctioning

I am having some trouble with my Toastr notification messages. While the message does display, I seem to be unable to make use of any options that I set. Despite specifying some options, they do not seem to work as intended. $('#editButton').c ...

Retrieving the ID and value of a specific dropdown list using jQuery from an array of dropdown lists

My HTML structure looks like this: <?php foreach($active_brand as $brand) { ?> <select name="selector" id="selector"> <?php foreach($options as $option) { ?> <option <?php if($brand['State'] == $option) { ?& ...

An addition operation in Javascript

Recently, I dipped my toes into the world of Javascript. However, I found myself puzzled by the script and its corresponding HTML output provided below. Script: <h1>JavaScript Variables</h1> <p id="demo"></p> <script> var ...

Testing asynchronous actions in React Redux

Currently encountering an error while testing async actions. The error message reads TypeError: Cannot read poperty 'then' of undefined, pointing to the line below in my code. return store.dispatch(actions.fetchMovies()).then(() => { Below i ...