Vue项目中PIXI.js数据变更时清除画布重绘及添加过渡效果问题
问题根因
你之前调用this.app.renderer.destroy()、this.app.renderer.clean()无效的核心原因是,这两个API是针对渲染器本身的销毁/帧缓存清理操作,并不会移除PIXI舞台(stage)中已经添加的子元素实例,你每次调用visualizeStuff都往舞台新增一批路径和精灵,旧实例一直保留,自然会出现内容叠加的问题。
问题1:旧内容无法清除的解决方案
首先优化元素管理逻辑,不要直接把动态生成的元素挂载到根stage上,新增一个专属的动态容器统一管理所有可变元素:
// data 中新增动态容器变量 data() { return { app: new PIXI.Application({ width: 800, height: 600, transparent: true }), currentYear: 2021, dynamicContainer: null, // 存储所有动态生成的元素 // ...other data... } }, mounted() { this.width = d3.select(this.$refs.container).node().getBoundingClientRect().width this.height = d3.select(this.$refs.container).node().getBoundingClientRect().height // 初始化动态容器并挂载到根舞台,只执行一次 this.dynamicContainer = new PIXI.Container() this.app.stage.addChild(this.dynamicContainer) // canvas 挂载也只执行一次,不要放到 visualizeStuff 里 this.$el.appendChild(this.app.view) this.visualizeStuff() },
然后将原来往this.app.stage添加元素的逻辑,全部改为往this.dynamicContainer添加,重绘前调用removeChildren()清空容器即可:
watch: { params: function(newVal, oldVal) { if (newVal.someValue == this.target) { this.currentYear = newVal.year // 清空所有旧动态元素 this.dynamicContainer.removeChildren() this.visualizeStuff() } } }, methods: { visualizeStuff() { this.app.renderer.resize(this.width, this.height) // ...过滤数据逻辑不变 for (let i = 0; i < filteredData.length; i++) { let path = new PIXI.Graphics() path.lineStyle(1, 0xff0000, 0) this.pathGenerator(filteredData[i].geometry, path) path.endFill() // 改为加到动态容器 this.dynamicContainer.addChild(path) let item = PIXI.Sprite.from(require("images/item.png")) // 改为加到动态容器 this.dynamicContainer.addChild(item) // ...GSAP 路径动画逻辑不变 } } }
问题2:添加过渡效果的实现方案
你已经在项目中使用GSAP,可以直接通过GSAP的透明度动画实现退场/入场效果,修改watch和元素生成逻辑即可:
// watch 中添加退场动画 watch: { params: async function(newVal, oldVal) { if (newVal.someValue == this.target) { this.currentYear = newVal.year // 存在旧元素时先执行退场动画 if (this.dynamicContainer.children.length) { await gsap.to(this.dynamicContainer.children, { alpha: 0, duration: 0.3, ease: "power1.out" }) // 动画结束后清空旧元素 this.dynamicContainer.removeChildren() } this.visualizeStuff() } } }, methods: { visualizeStuff() { this.app.renderer.resize(this.width, this.height) // ...过滤数据逻辑不变 for (let i = 0; i < filteredData.length; i++) { let path = new PIXI.Graphics() path.lineStyle(1, 0xff0000, 0) this.pathGenerator(filteredData[i].geometry, path) path.endFill() path.alpha = 0 // 初始透明度设为0 this.dynamicContainer.addChild(path) let item = PIXI.Sprite.from(require("images/item.png")) item.alpha = 0 // 初始透明度设为0 this.dynamicContainer.addChild(item) // ...GSAP 路径动画逻辑不变 } // 所有新元素生成完成后执行入场动画 gsap.to(this.dynamicContainer.children, { alpha: 1, duration: 0.3, ease: "power1.in", stagger: 0.05 // 可选:元素错开入场,不需要可删除 }) } }
注意事项
- 组件销毁时需要调用
this.app.destroy(true)清理PIXI实例,避免内存泄漏 - PIXI实例不要放在Vue的响应式对象中,避免被Proxy代理导致性能问题或异常
- 如果路径动画是持续运行的,清空元素前需要先销毁对应GSAP动画实例,避免内存泄漏
内容的提问来源于stack exchange,提问作者Anton
相关产品推荐
相关产品推荐

