How to use OrbitControls with CSS2DRenderer

Hello, that is a topic about how to solve this problem. I got this problem a few hours ago and it was a bit annoying but finally I got why it was happening and I want to share this with you to save you time in the future.

First at all, if you don’t know what’s CSS2 it’s mostly used to render HTML elements on your 3D model using Three.js. It’s uses a pararell render that is CSS2 or CSS3 render

An example of both are here:

and this one for CSS3D

So following the topic of this article, when you set up your text on your 3D model

import { CSS2DRenderer, CSS2DObject } from '';

const textDiv = document.createElement( 'div' );
  textDiv.className = 'label';
  textDiv.textContent = "hehe"; = '-1em';
  const textLabel = new CSS2DObject( textDiv );
  textLabel.position.set( 0.1, 0.1, 0.1 );


  labelRenderer = new CSS2DRenderer(canvas);
  labelRenderer.setSize( window.innerWidth, window.innerHeight ); = 'absolute'; = '0px';

 document.body.appendChild( labelRenderer.domElement );

As you can see all the code is pretty easy, I use model.load whats is a gltf.scene casue I am using a GLTF file as 3D object.

One important thing to say is that you should put this code:

 document.body.appendChild( labelRenderer.domElement );

As it’s cause if not you won’t see your text, this is not for the 100% of cases, on mine I am using a canvas that have a fullscreen size.

And if you already had orbit controls settled on your last render as I had, your probabbly should had this code:

controls = new OrbitControls(camera, renderer.domElement);

At this point i was facing that my orbit control doesnt work, and that’s cause CSS2 render put another layer of render at the top of the layers so your orbit control what’s is charged on the renderer layer is unable to track your movements cause you are moving your mouse on the CSS2 render.

And how do you solve it?

In my case as I explained up in the article, was solved just changing the render where my orbit control tracks, this way:

controls = new OrbitControls(camera, labelRenderer.domElement);

And now it’s fully works as it used to do, but now I have the HTML div on my 3D model as I wanted.

I hope this was useful for anyone, and also thanks to the Three.js Community for help on other to release their projects using this brilliante library.


In both examples mentioned above when I zoom in, it’s not displaying a label.
Is there any method to disable the scaling factor for label on-camera zoom?

I think using = 'none'

Would be better, as it disables the events for the label layer, so returning things to what they were before.

1 Like

Thanks, i was able to add text in my scene
but what if i want to add a shape instead, an animated circle svg. how should i do that

…what if you actually want pointer events?

For being able to interact with the HTML content rendered in the 3D scene, you can use ion 3D Engine as it renders the HTML as texture on a plane mesh.

Thank you so much for this fix! Would have saved me a couple of hours of googling and debugging if I’d found it sooner haha.

Exactly where I am stuck. Were you able to figure how to set up pointer events on the CSS2DObjects while the CSS2DRenderer placed on top of normal renderer?

hello, thanks for sharing, i want ask a question.

i have view this website:

how set postion each div follow each child model (build)?

Both css2d and css3d just project on top of the canvas, you can’t use it with any control because it could never occlude scene objects.

Consider using Drei/html, the html becomes an actual part of your scene

It does that with a blending trick. The html is technically behind the canvas but it’s cutting a hole into it, that hole gets occluded by scene meshes.

If you want pointer events on both html and canvas then both must share the same parent, the canvas and controls must get their events from that parent or else either the canvas or the html overlay will block events for its sibling