Let me start by saying I have a lot to learn. I hate asking for help, but I have went through pages and pages looking for an answer with no luck.
I am sure the issue is simple, but can someone please explain to me what I am doing wrong? My Three JS atom loads just fine. I dont have any issue until i navigate to a different page (NuxtLink) and then return to this page.
It throws this error:
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'width') at new WebGLRenderer (three.module.js:26639:23) at Atom.vue:64:1 at hook.__wdc.hook.__wdc (runtime-core.esm-bundler.js:2626:20) at callWithErrorHandling (runtime-core.esm-bundler.js:155:22) at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:164:21) at hook.__weh.hook.__weh (runtime-core.esm-bundler.js:2684:29) at flushPostFlushCbs (runtime-core.esm-bundler.js:341:32) at flushJobs (runtime-core.esm-bundler.js:395:9)
Which to my understanding means that the canvas isnt loading ? I even tried using the options API instead of the composition API, but I get the same issue. Ive tried using onActivated & KeepAlive as you can see and neither of those work too.
<script setup>
import * as THREE from 'three'
onActivated(() => {
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Sizes
const sizes = {
width: 300,
height: 300
}
const pixelRatio = window.devicePixelRatio
/* Base */
// Scene
const scene = new THREE.Scene()
/* Props */
// Plane Background
const backgroundPlane = new THREE.PlaneGeometry(50, 50, 4, 4)
const backgroundMaterial = new THREE.MeshStandardMaterial({ emissive: "#000505", roughness:0.1, metalness: 1 })
const background = new THREE.Mesh(backgroundPlane, backgroundMaterial)
background.position.z = -5
scene.add(background)
// Atom Group
const atom = new THREE.Group()
scene.add(atom)
atom.position.z = -2
const protonSphere = new THREE.SphereGeometry(.3, 16, 16)
const protonMaterial = new THREE.MeshStandardMaterial({ emissive: "#080E54", roughness:0.4, metalness: 0.9 })
const proton = new THREE.Mesh(protonSphere, protonMaterial)
atom.add(proton)
const electronCloudSphere = new THREE.SphereGeometry(2, 32, 32)
const electronCloudMaterial = new THREE.MeshPhysicalMaterial({ opacity: .2, transparent: true, reflectivity:0, side:THREE.DoubleSide, roughness:0.5, metalness: 0, ior: 2, thickness: 2, transmission: 1 })
const electronCloud = new THREE.Mesh(electronCloudSphere, electronCloudMaterial)
atom.add(electronCloud)
const electronSphere = new THREE.SphereGeometry(.1, 16, 16)
const electronMaterial = new THREE.MeshStandardMaterial({ emissive: "#2FFFFF", roughness:0, metalness: 0.9 })
const electron = new THREE.Mesh(electronSphere, electronMaterial)
const radius = 1.5
let theta = Math.random() * Math.PI * 2
let phi = Math.random() * Math.PI
const x = radius * Math.sin(theta)
const z = radius * Math.cos(theta)
const y = radius * Math.cos(phi)
electron.position.set(x, y, z)
atom.add(electron)
const pointLight = new THREE.PointLight("#B7DBFF", 1, 5)
pointLight.position.z = 1
scene.add(pointLight)
/* Camera & Controls */
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.z = 3
scene.add(camera)
/* Renderer */
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(pixelRatio, 2))
/* Animate */
const clock = new THREE.Clock()
let time = 0
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
const deltaTime = elapsedTime - time
time = elapsedTime
// Animations
let shake = (Math.random() * 0.015)
proton.rotateX(.5)
proton.position.set(shake, shake, 0)
atom.rotateX(.01 * Math.random())
atom.rotateY(.02 * Math.random())
atom.rotateZ(.04 * Math.random())
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
})
</script>
<template>
<div >
<canvas id="atom" ></canvas>
</div>
</template>
CodePudding user response:
Your issue is happening because you do indeed have a querySelector, while the recommended way is to go with template refs: https://vuejs.org/guide/essentials/template-refs.html#template-refs
The reason being is that when you are in an SPA context, the document
stays the same but stacking querySelectors and trying to use them will not behave properly with Components lifecycles.
Even worse with Nuxt, because you don't actually have document
on the server, so it may cause even more bugs or even memory leaks.
mounted
is called only on the client-side, so that's why it's working great down there. But the recommended way is still to use template refs, to avoid any issue (and remove the need to remove the listeners).
CodePudding user response:
Finally figure it out.
const webgl = ref(null)
onMounted(() => {
// Canvas
const canvas = webgl.value
Using template refs seems to fix my issue.
<template>
<div >
<canvas ref="webgl" id="atom" ></canvas>
</div>
</template>
Not sure if this is the best solution... but it seems to work!