我在vue 3中使用PIXI js(下面的示例代码)因为大多数图形都遵循类似的模式,具有不同的行为和 prop ,我们选择了使用typescript的OOP路线,以避免代码重复。但是这样做,或者我们无法分辨出什么是错误的,会导致像cannot find propagation path to disconnected target
这样的错误。
代码中的注解提供了详细信息
在深入研究之前,
当DisplayObject被创建为类的属性时,或者从父类事件侦听器重写时,错误cannot find propagation path to disconnected target
遇到
更改显示对象的属性也不会导致其重新呈现
<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' // usage here because it's 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.viewport.drag().pinch().wheel().decelerate()
this.elem = elem
;(globalThis as any as GlobalThis).__PIXI_APP__ = this.app //for debugging w/ pixi devtools
}
init() {
this.render()
const background = this.drawBackground() // event listeners on app.stage didn't run an so this was done as a hack
this.startEventHandlers(background)
}
render() {
this.elem.appendChild(this.app.view)
this.app.stage.addChild(this.viewport)
}
drawBackground() {
// using background as a property of the class caused eventListeners to not be fired properly, actually at all
// error regarding propagation occurs
const background = new Graphics()
background.beginFill(0x000000)
background.drawRect(0, 0, this.elem.clientWidth, this.elem.clientHeight)
background.endFill()
background.eventMode = 'static'
// this.app.stage.addChild(background)
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()
if (mainStore.elementToAdd !== null) {
mainStore.elementToAdd.position.set(Math.round(event.globalX), Math.round(event.globalY))
const elem = mainStore.elementToAdd.draw() //
/*
draw draws out the graphics and returns it, this was another hack as returning the whole container caused the events launched from the graphics object to error
*/
if (callback) {
callback(elem)
}
mainStore.onScreenElements.push(mainStore.elementToAdd)
mainStore.elementToAdd = null
return
}
...
mainStore.currentlySelectedElement = null
}
export const onPointerUp = (event: FederatedPointerEvent) => {
const mainStore = useMainStore()
if (useMainStore().draggingElement !== null) {
useMainStore().draggingElement = null
}
}
export const onPointerMove = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => {
if (useMainStore().draggingElement !== null) {
useMainStore().draggingElement!!.move(event.globalX, event.globalY) // nothing is updated on screen
console.log(useMainStore().draggingElement!!.position) // but the x, y of the container changes
/*
trying to call element.parent yields null, and errors because it has lost reference to it's parent
*/
}
}
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
id: number = Math.floor(Math.random() * 1000000)
readonly store = defineStore(`${this.constructor.name}-${this.id}`, () => {
const name = ref(this.name)
const value = ref<{ [key: string]: { [key: string]: string } }>({})
return { name, value }
})
protected constructor(x: number = 0, y: number = 0, name: string, nodes: number[]) {
super()
this.nodes = nodes
this.x = x
this.y = y
this.useStore = this.store()
this.useStore.$patch({
name: name
})
}
abstract draw(): Graphics
initializeEventListeners(graphic: Graphics) {
// this function is called in the draw method and the graphic object passed as argument
graphic.hitArea = graphic.getBounds()
graphic.eventMode = 'static'
graphic.on(
'pointerdown',
() => {
if (useMainStore().currentAction === 'move') {
useMainStore().draggingElement = this
} else {
useMainStore().currentlySelectedElement = this
}
},
this
)
graphic.on('pointerup', () => {
console.log('pointer up')
})
}
move(x: number, y: number) {
this.x = x
this.y = y
}
}
样本元素
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() {
// plan was to make all graphics properties of the parent class, but doing so just prevent all events from being triggerd
const resistorGraphic = new Graphics()
const body = new Graphics()
resistorGraphic
.lineStyle(DIMENSIONS.CONDUCTOR_WIDTH, COLORS.CONDUCTOR)
.moveTo(this.x, this.y)
...
body.addChild(resistorGraphic)
this.addConductor().forEach((conductor) => {
body.addChild(conductor)
})
this.initializeEventListeners(resistorGraphic)
return body
}
}
画布上的元素是如何添加的
用户需要从多个元素中进行选择。在选择时,将使用所选元素创建一个新对象,并将其添加到全局pinia存储示例中,如
mainStore.setElementToAdd(new Resistor(...))
setElementToAdd(element: BaseElement) {
...
state.elementToAdd = element
...
}
1条答案
按热度按时间tjvv9vkg1#
几周后,我们意识到,对象在创建后被垃圾收集,
pinia
只存储对象的代理,而不是原始对象。(这是一个假设)结果是重构使
VisualisationEngine
类成为单例,所有pixijs对象都存储在该类中底线是,vue的React系统在这里并不好用