13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Dispose three.js objects with Nuxt

Last updated at Posted at 2019-12-24

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 の展開を待ってから実行するなど考慮しておくとよいですね。

pages/xxx.js
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()
    },
  },
}
assets/NiceThreeScene.js
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 でした。

おわります。
メリークリスマス🎅🏻

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?