LoginSignup
1
0

More than 1 year has passed since last update.

【Vuetify + Three.js】Vuetifyのダイアログ(モーダル)上でThree.jsのシーンを表示する

Posted at

What`s the problem?

以下のようなコードを書いてVuetifyのDialog componentの中でThree.jsのシーンを表示させようとすると上手くいかなかった。

コード
App.vue
<template>
  <v-app>
    <v-row align="center">
      <v-col align="center">
        <v-dialog v-model="dialog">
          <template v-slot:activator="{ on, attrs }">
            <v-btn color="primary" dark v-bind="attrs" v-on="on">
              Open Dialog
            </v-btn>
          </template>

          <v-card>
            <v-card-title>Three.js in dialog</v-card-title>
            <!-- ここにシーンを表示したい -->
            <v-container>
              <canvas ref="canvasRef" class="canvas"></canvas>
            </v-container>
          </v-card>
        </v-dialog>
      </v-col>
    </v-row>
  </v-app>
</template>

<script>
import * as THREE from "three";

export default {
  name: "App",

  data() {
    this.scene = null;
    this.camera = null;
    this.renderer = null;
    this.cube = null;

    return {
      dialog: false,
    };
  },

  methods: {
    // 必要なインスタンスを初期化
    init() {
      this.scene = new THREE.Scene();
      this.camera = new THREE.PerspectiveCamera(75, 2, 0.1, 1000);
      this.renderer = new THREE.WebGLRenderer({ canvas: this.$refs.canvasRef });
      this.renderer.setSize(1000, 500);

      const geometry = new THREE.BoxGeometry();
      const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
      this.cube = new THREE.Mesh(geometry, material);
      this.scene.add(this.cube);

      this.camera.position.z = 5;
    },

    // アニメーション
    animate() {
      requestAnimationFrame(this.animate);
      console.log(this.cube.rotation.x);
      this.cube.rotation.x += 0.01;
      this.cube.rotation.y += 0.01;
      this.renderer.render(this.scene, this.camera);
    },
  },

  watch: {
    // ダイアログが開いたら初期化してアニメーションを開始
    dialog: function (val) {
      if (val) {
        this.init();
        this.animate();
      }
    },
  },
};
</script>

<style scoped>
.canvas {
  display: block;
  margin-left: auto;
  margin-right: auto;
}
</style>
結果

qiita1.gif

Solution

シーンの初期化とアニメーションを行う際に、setTimeout()関数を利用すると上手く表示することができた。

コード
App.js
<template>
  // 略
</template>

<script>
import * as THREE from "three";

export default {
  // 一部略

  watch: {
    // ダイアログが開いたら初期化してアニメーションを開始
    dialog: function (val) {
      if (val) {
        // 変更点はここ!
        setTimeout(() => {
          this.init();
          this.animate();
        });
      }
    },
  },
};
</script>

<style scoped>
// 
</style>
結果

qiita2.gif

仕組み

そもそもの話、ダイアログの中になぜシーンを表示できないかと言うと、Vuetifyのダイアログを開く処理と並行してThree.jsのシーン等を初期化する処理を走らせていることが原因らしい。(詳細な仕組みについては分からないが、なんとなくダメそうな雰囲気は確かにある)

setTimeout()は通常第二引数に時間を与えることで指定した秒数だけ待機した後に関数を実行してくれる。しかし、第二引数を与えない or 0を与えるとできるだけ早く実行する、つまりダイアログを開く処理が完了するまで待機してくれるようになるため、ダイアログを開く処理とThree.jsの初期化処理が競合せずに済むようになる。

余談

筆者はこの問題を解決するために1週間無駄にしました()

「setTimeoutで無理やり描画を遅延させれば何とかなるんじゃね!?」という浅はかな考えの元0.1秒程度遅延させてみた結果偶然にも上手くいき、その後第二引数に0を与えても同様の結果が得られることに気付き、そこで初めてsetTimeout()の上記の仕様を知り「なるほどなぁ」という気持ちになりました。そもそも第二引数を省略可能だなんて知らなかった...

本記事は東京高専プロコンゼミ② Advent Calendar 2022の25日目の記事です。昨日の記事と合わせて2日連続で個人的なトラブルシューティングをアドベントカレンダーに投稿するという暴挙となりましたが、将来これらの記事が誰かの役に立てればと思います。

1
0
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
1
0