Make CSS2DObject follow cursor

Hi there!
What’s the best way to make a CSS2DObject label follow the cursor?

The primary purpose of CSS2DObject is to transform a 3D translation into a CSS 2D translation that can be applied to a HTML element. So normally, you add an instance of CSS2DObject to an instance of Object3D.

If you just need an HTML that follows the mouse, you can do this:

const element = document.querySelector( '#div' );

document.addEventListener( 'mousemove', event => {

	element.style.transform = `translate(-50%,-50%) translate(${event.pageX}px, ${event.pageY}px)`;

} );

Live demo: https://jsfiddle.net/hck72rw9/

2 Likes

Thank you for this!
Sadly, my OrbitControls doesn’t work anymore after implementing this.
Can you help?

It seems the div tag blocks all click events so the underlying canvas can’t process them anymore. It seems this can be fixed by adding pointer-events: none; to the div’s CSS.

Updated demo: https://jsfiddle.net/e15398cq/

1 Like

Thank you again, really appreciate your help!

I’m trying to make the div (#newlabel) only follow the mouse if there’s an intersection with an object in the scene and if there isn’t, make the following stop. This is my code so far:

const element = document.querySelector('#newlabel');

	//RAYCAST
	function raycast1() {

		raycaster1.setFromCamera(mouse, camera);

		let intersects = raycaster1.intersectObjects(objects, true);

		if (intersects.length > 0) {
			if (INTERSECTED != intersects[0].object) {
				if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
				INTERSECTED = intersects[0].object;
				INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
				INTERSECTED.material.emissive.setHex(0xff0000);
                //element.style.transform = `translate(-50%,-50%) translate(${event.pageX}px, ${event.pageY}px)`;
				document.addEventListener('mousemove', event => {

					element.style.transform = `translate(-50%,-50%) translate(${event.pageX}px, ${event.pageY}px)`;

				});
			}

		}
		//Un-highlights last selected object
		else {
			if (INTERSECTED) {
				INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
				document.removeEventListener('mousemove', event => {

					element.style.transform = `translate(-50%,-50%) translate(${event.pageX}px, ${event.pageY}px)`;

				});
			}
			INTERSECTED = null;
		}
	}

The following works but it doesn’t stop when there is no intersection anymore.

Can you help?

Right now, you are adding an event listener for each intersection. Please don’t do that! It’s only a matter of time until your application will crash.

Register the event listener once and use a flag that indicates whether an intersection has happened or not. You can then evaluate the flag like so:

document.addEventListener( 'mousemove', event => {

    if ( isIntersection === true ) {

        element.style.transform = `translate(-50%,-50%) translate(${event.pageX}px, ${event.pageY}px)`;

    }

} );
1 Like

Haha thank you! Don’t know why I didn’t figure this out myself.

I’m trying to set the div to visible after it translated to the mouse’s transform:

    var isIntersection = false;
    var labelX;
    var labelY;
    var marginX = 10;
    var marginY = 10;
    var factorX = 1;
    var factorY = 1;

    document.addEventListener('mousemove', event => {

        if (isIntersection === true) {

            labelX = event.pageX + factorX * (element.offsetWidth / 2) + marginX;
            labelY = event.pageY + factorY * (element.offsetHeight / 2) + marginY;
            element.style.transform = `translate(-50%,-50%) translate(${labelX}px, ${labelY}px)`;

        }

    });

    if (element.style.transform == `translate(-50%,-50%) translate(${labelX}px, ${labelY}px)`) {
        element.style.opacity = 1;
    }

Somehow this doesn’t work.
Can you help?

Somebody able to help? :slight_smile:

It seems it’s not clear what mean by that. How about updating the previous live example and then explain in detail the current and expected behavior?

Side note: If you are not getting a response in multiple days, it’s often a sign that your problem description is hard to understand or misses details.

1 Like

Trying to set up the example I already struggle to push the mesh to an array that the raycaster can use, as you can see in this live example. :smiley: I’m using a gltf in my app without any issues when pushing it to the array. :man_shrugging:

Hope you can help! :slight_smile:

Are you looking for something like this:

https://jsfiddle.net/en81u3qw/

1 Like

Close, yes! :raised_hands:
But I want the element not to be displayed until the raycaster hits the object, so not even on start. Setting display=“none” in HTML and then to “initial” in JavaScript didn’t work for me (still in the code but commented out here).

Hope you can help! :slight_smile:

Try it with opacity instead: three.js dev template - module - JSFiddle - Code Playground

If you use display, the value will always stay on none and thus the element will be invisible all the time.

1 Like

That’s it! :tada: :tada: :tada: Thank you very much for all your help :pray:

1 Like