Nuxt のページ遷移時に three で作ったオブジェクトを破棄したいとき、メモリリークをケアするための tips です。
少し前にちょうどアドベカレンダーで次の three x Nuxt な記事が書かれていました。
この内容で Nuxt で three をやっていくのはつかめると思うので、破棄部分のみ書いていきます。先述の記事には EventBus で Vue コンポーネント側から three の描画スクリプトのメソッドを発火させていましたが、Vue から剥がしやすくしておくために、今回は data にインスタンスを格納して取り回していくことを想定しています。
パフォーマンス考えるならやはり Vue に依存しない自前の EventBus で実装するとか Three の EventDispatcher
を使うのがよいかなと思います。もしくは Non-reactive data とか。(場合によりけりですね、Vue 縛りなら先述の記事のように対応するのが一番簡単でスマートかと … また追記します)
Page/xxx.vue の beforeDestroy
で破棄メソッドを実行します。このときcanvas が消えるので、UX を考えると、先に loader の展開を待ってから実行するなど考慮しておくとよいですね。
import { NiceThreeScene } from '~/assets/js/NiceThreeScene'
export default {
data() {
return {
canvas: null,
frameId: 0,
}
},
mounted() {
this.init()
},
beforeDestroy() {
this.canvas.finish()
cancelAnimationFrame(this.frameID)
},
methods: {
init() {
const { canvas } = this.$refs
this.canvas = new NiceThreeScene({ canvas })
//
this.update()
},
update() {
this.frameId = requestAnimationFrame(this.update)
this.canvas.update()
},
},
}
export class NiceThreeScene {
constructor({ canvas }) {
// ... init ...
}
/**
* called by raf
*/
update() {
if (!this.needsStopUpdate) {
const time = 0.001 * performance.now()
this.animationObjects.update(time)
//
this.renderer.render(this.scene, this.camera)
}
}
/**
* called by beforeDestroy
*/
finish() {
this.needsStopUpdate = true // stop update method
//
this.disposeThreeObjects(this.scene, this.renderer) // dispose
//
this.canvas.width = 1 // resize canvas
this.canvas.height = 1 // resize canvas
}
disposeThreeObjects(scene, renderer) {
scene.children.forEach(obj => {
obj.traverse(obj3D => dispose(obj3D))
scene.remove(obj)
})
renderer.dispose()
renderer.forceContextLoss()
renderer.domElement = null
}
}
function dispose(obj) {
if (obj.geometry) {
obj.geometry.dispose()
obj.geometry = null
}
if (!!obj.material && obj.material instanceof Array) {
obj.material.forEach(material => disposeMaterial(material))
} else if (obj.material) {
disposeMaterial(obj.material)
}
}
function disposeMaterial(material) {
if (material.map) {
material.map.dispose()
material.map = null
}
material.dispose()
material = null
}
ざっくりまとめると update の処理をスキップ、 three のオブジェクトを破棄して canvas をリサイズする流れです。
こちら Three.js Advent Calendar 2019 12.24 でした。
おわります。
メリークリスマス🎅🏻