THREE.JS OrbitControls not working when DOM Element is positioned over the scene

Issue

I’m trying to place labels as element with position:absolute; over a THREE.JS scene. The problem is that when the mouse is over one of the labels (the red box in the exemple below), the events triggering OrbitControls are “stopped” by the labels and do not propagate to the Renderer.

I’ve reproduced a minimal version of code to see the problem.

enter image description here

<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style>
        body {
            margin: 0px;
            overflow: hidden;
        }

        #overlay {
            position: absolute;
            top: 40%;
            left: 40%;
            width: 20%;
            height: 20%;
            background-color: #f00;
            padding: 3%;
            text-align: center;
            color: #fff;
            box-sizing: border-box;
        }
    </style>
</head>

<body>
    <div id="container"></div>

    <!-- This div below stop the OrbitControls events, why? -->
    <div id="overlay">I am a div with position:absolute</div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/r87/examples/js/controls/OrbitControls.js -->
    <script src="orbit-controls.js"></script>
    <script>
        var container;
        var camera, scene, renderer;
        var uniforms, material, mesh;
        var controls;
        init();
        animate();

        function init() {
            container = document.getElementById('container');
            var aspect = window.innerWidth / window.innerHeight;
            camera = new THREE.PerspectiveCamera(45, aspect, 0.1, 1500);
            camera.position.set(1, 1, 1);
            scene = new THREE.Scene();
            renderer = new THREE.WebGLRenderer();
            container.appendChild(renderer.domElement);
            renderer.setSize(window.innerWidth, window.innerHeight);
            controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);

            var geometry = new THREE.BoxGeometry(1, 1, 1);
            var material = new THREE.MeshBasicMaterial({
                color: 0x00ff00
            });
            var cube = new THREE.Mesh(geometry, material);

            scene.add(cube);
        }

        function animate() {
            requestAnimationFrame(animate);
            render();
        }

        function render() {
            renderer.render(scene, camera);
        }
    </script>
</body>

</html>

Here is a link to a similar project where the labels doesn’t stop the propagation of the event so that the camera can follow the mouse interaction. However I have not found what makes this exemple work and mine not.
http://armsglobe.chromeexperiments.com

What to do so that the OrbitControls still works behind the <div> labels ?

Solution

To summarize answers from comments:

If you don’t need any mouse-events from the overlays, it is easiest to just disable event-handling via css:

<div class="overlay" style="pointer-events: none">...</div>

You can also use a common parent-element for event-handling:

<div class="parent" style="position:relative">
  <canvas ... /> <!-- this is the canvas from renderer.domElement -->
  <div class="overlay"></div>
</div>

and for the controls

const controls = new THREE.OrbitControls(camera, document.querySelector('.parent'));

this way all mouse-events reaching the parent (which includes events from the overlays) will be handled by the controls (a special case of this is handling all events at the root-level, by omitting the dom-element (or specifying document.documentElement) for the OrbitControls constructor).

If you want to handle certain events from the overlays, like click for instance, you can prevent them to get forwarded to the orbit-controls using stopPropagation():

overlayEl.addEventListener('click', event => {
  event.stopPropagation();

  // handle event
});

Answered By – Martin Schuhfuß

Answer Checked By – Candace Johnson (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.