Make only the ThreeJS model and elements underneath the canvas interactive

I am new to ThreeJS, so excuse me if this is a dumb question. I have a canvas that takes up the whole viewport to keep things simple. The canvas is placed at the top of the stacking context so that the model is always visible or never obscured by other elements on the page. However, because of this, the canvas captures all of the events on the page. I know that I can assign a pointer-events: none to the canvas, but I would like both the model and the rest of the webpage underneath the canvas to be interactive. Is there any way to ensure only the model is interactie, whereas the rest of the canvas acts as if it has pointer-events: none so that the user can interact with the webpage itself?

More context: I am loading the model using the GLTFLoader. The model covers only a small portion of the middle of the canvas. The ability to rotate the model in any direction in the centre of the screen via OrbitControls is the only interaction that I intend to implement on the model itself. Also, I plan to implement non-interactive animations in response to certain events.

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

loader.load('../assets/model/it.glb', function(gltf){
  gltf.scene.rotation.y = 0.5 * Math.PI // rotate model's starting position
}, undefined, function (error) {

you need to register events on the parent that holds both the canvas and the dom layer on top (or behind). both the canvas and the dom layer will have to be position:absolute. the events on the canvas now have to use clientX and clientY, not offsetX/Y! because if the mouse would hover the top/left of a dom element, oX/Y would be 0/0 regardless where the node is positioned.

some proof of concept, just to show it’s possible


I do not know React yet, but I realized I can simply assign a pointer-events:none and then use normal JS events like mousemove to call a function that manipulates the object. Thanks for taking the time to help nonetheless!

One other question if you do not mind:

I am currently manipulating the camera.position in relation to mousemove event.clientX and event.clientY, but this only tilts the bottle. How can I replicate the behaviour of OrbitControls so that the bottle appears to spin or rotate in its place?

I have tried using camera.rotation.set, but this does not seem to do anything. I read somewhere that controls.update() might override whatever rotation settings that are applied, but removing controls.update() causes the model to float around the screen in a 2D manner instead.

addEventListener('mousemove', mouseMove)

function mouseMove(e) {
    mouseX = -( ((e.clientX / innerWidth) * 2 - 1) / 4 ),
    mouseY = ((e.clientY / innerHeight) * 2 - 1) / 4
  camera.position.set(mouseX, mouseY, 1)
  camera.rotation.set(0, 0, mouseX * Math.PI) // not sure why this does not work or what to do here...

function animate() {
  renderer.render(scene, camera)

the issue mainly resolves around how the dom works. the first part decribed that, the examples are just poc’s so that you see how it more or less works but you will implement it the same way: a shared parent with absolute children and using client coordinates, not offset.

as for the 2nd question, you need to center an object in order to spin it around itself. for that you best use blender, or, you have to compute the object bounds via THREE.Box3 and then shift the mesh to the negative remainder.

I ended up replicating the functionality of TransformControls to accomplish the model being able to rotate in its place in any direction. I believe I might have to rebuilt the 3D model because I cannot for the life of me figure out how to lower the model downwards in relationship to the viewport without tilting the bottle. Have tried lowering the gltf.scene.position.y and the camera.position.y to no avail. Any idea?

Raycaster is where it’s at!