0

I am having trouble with an implementation of Nuxt 3 and Threejs.

<template>
  <div>
    <canvas ref="webgl" class="canvas"></canvas>
  </div>
</template>

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

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
const webgl = ref(null);

var renderer;
onMounted(async () => {
  await nextTick();
  console.log();
  if (process.client) {
    const canvas = document.querySelector(".canvas");
    //const canvas = webgl.value; Silent error but does not work
    console.log(canvas);
    //Getting canvas element

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(32, 1, 0.1, 1000).translateZ(15);

    renderer = new THREE.WebGLRenderer({
      canvas,
      alpha: true,
      antialias: true,
    });

    // Creating a renderer
    renderer.setClearColor(0x000000, 0);
    renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
    renderer.setPixelRatio(window.devicePixelRatio);

    // Avoid pixelation on resize
    window.onresize = () => {
      renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
    };

    // Creating loader for custom GLTF models
    const loader = new GLTFLoader();
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
    loader.setDRACOLoader(dracoLoader);

    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.9);
    hemiLight.position.set(0, 0, 15);
    scene.add(hemiLight);

    let model;
    loader.load(
      // resource URL
      "/models/molecule.glb",
      // called when the resource is loaded
      function (gltf) {
        model = gltf.scene;
        model.position.set(0, -0.4, -1);
        if (model) model.rotation.x += 90;
        scene.add(model);
      },
      // called while loading is progressing
      function (xhr) {
        console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
      },
      // called when loading has errors
      function (error) {
        console.log("An error happened", error);
      }
    );
    let x = 0;

    // start the loop
    renderer.setAnimationLoop(() => {
      if (model) {
        // --- Simple Rotation Animation
        // model.rotation.y += 0.002;
        // if (model.rotation.y >= 360) model.rotation.y = 0;
        // if (model.rotation.z >= 0) model.rotation.z -= 0.002;
        // else if (model.rotation.z <= 0) model.rotation.z += 0.002;
        // else {
        //   model.rotation.z += 0.0002;
        // }
        // --- Floating Animation
        x = x + 2;
        model.position.y = Math.cos(x * 0.01) * 0.3;
        model.rotation.z = Math.sin(x * 0.01) * 0.2;
        model.rotation.x = Math.sin(x * 3.14 * 0.01) * 0.02 + 90;
      }
      renderer.render(scene, camera);
    });
  }
});

onUnmounted(() => {
  renderer.setAnimationLoop(null);
});
</script>

<style scoped>
.canvas {
  width: 100%;
  height: 100%;
  z-index: 1;
}
</style>

I use this code to load a 3d model inside a vue component, which wraps an html canvas. Which then is animated and mounted on the page.

If I load the page normally, then everything works, and the animation with the model is shown. But if I navigate to another page and back (via the Vue/Nuxt router)

I get the following error: enter image description here

As if the html canvas did not exist at all. Even though I am declaring it after the component is mounted...

I also tried using template refs, but then the model just does not load, and there is no error too.

Oh and for the record, I am running the app on SSR mode.

I will be forever grateful if you can help me. Thanks in advance!

kissu
  • 40,416
  • 14
  • 65
  • 133
  • Is the `canvas` variable correctly loaded when SPA navigating? What's exactly the line 27 in `molecule.vue`? – Kapcash Jun 13 '22 at 07:28
  • Hey @Kapcash, thanks for your comment. Technically the component is supposed to be mounted again at every SPA navigation, am I right? Line 27 is: `const renderer = new THREE.WebGLRenderer({canvas, alpha: true, antialias: true });` The renderer, which complains, because canvas is `null`, because the `document.querySelector()` is not working. – Mattia Peiretti Jun 13 '22 at 08:06
  • Yes, it's mounted again on every page navigation unless you're using the `` vue component (which won't trigger the mounted hook more than once). If you set a breakpoint on your `onMounted` function in the devtools, do you actually see the `canvas` in the DOM? – Kapcash Jun 13 '22 at 08:41
  • Hey Kapcash, I put a breakpoint especially on the line `const canvas = document.querySelector("#canvas");` inside the `onMounted` function. When navigating the page, the breakpoint is hit still before the view changes (before the new page is loaded). That is weird isn't it? But I guess that's why. – Mattia Peiretti Jun 15 '22 at 00:36
  • It is not normal indeed. Can you try to make a reproduction repo on codesandbox, to see if the `onMounted` hook is also called before the page change? Maybe it's a Nuxt 3 bug. – Kapcash Jun 15 '22 at 09:58
  • For anyone else who made it here with the same problem, there's a relevant ticket here: https://github.com/nuxt/framework/issues/3587 – Squimon Aug 05 '22 at 04:12

0 Answers0