Three JS Keep Label Size On Zoom

I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that.

And it's very simple. Let's say, a THREE.Sprite() object (label) is a child of a THREE.Mesh() object (planet), then in your animation loop you need to do

var scaleVector = new THREE.Vector3();
var scaleFactor = 4;
var sprite = planet.children[0];
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
sprite.scale.set(scale, scale, 1); 

I've made a very simple example of the Solar System, using this technique.


For the benefit of future visitors, the transform controls example does exactly this:

https://threejs.org/examples/misc_controls_transform.html

Here's how its done in the example code:

var factor;
if ( this.camera.isOrthographicCamera ) {
	factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
} else {
	factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
}
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );

Finally I found the answer to your question:

First, create a DOM Element:

<div class="element">Not Earth</div>

Then set CSS styles for it:

.element {position: absolute; top:0; left:0; color: white}
//        |-------------------------------|  |-----------|
//            make the element on top of       canvas is 
//               the canvas                   black, so text
//                                            must be white

After that, create moveDom() function and run it every time you render the scene requestAnimationFrame()

  • geometry is the geometry of the mesh
  • cube is the mesh you want to create label
    var moveDom = function(){
    vector = geometry.vertices[0].clone();
    vector.applyMatrix4(cube.matrix);
    vector.project(camera);
    vector.x = (vector.x * innerWidth/2) + innerWidth/2;
    vector.y = -(vector.y * innerHeight/2) + innerHeight/2;
    //Get the DOM element and apply transforms on it
    document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
    document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
    };

You can create a for loop to set label for all the mesh in your scene.

Because this trick only set 2D position of DOM Element, the size of label is the same even if you zoom (the label is not part of three.js scene).

Full test case: https://jsfiddle.net/0L1rpayz/1/

var renderer, scene, camera, cube, vector, geometry;

var ww = window.innerWidth,
	wh = window.innerHeight;

function init(){

	renderer = new THREE.WebGLRenderer({canvas : document.getElementById('scene')});
	renderer.setSize(ww,wh);

	scene = new THREE.Scene();

	camera = new THREE.PerspectiveCamera(50,ww/wh, 0.1, 10000 );
	camera.position.set(0,0,500);
	scene.add(camera);

	light = new THREE.DirectionalLight(0xffffff, 1);
	light.position.set( 0, 0, 500 );
	scene.add(light);

	//Vector use to get position of vertice
	vector = new THREE.Vector3();

	//Generate Not Earth
	geometry = new THREE.BoxGeometry(50,50,50);
	var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
  cube = new THREE.Mesh(geometry, material);
  scene.add(cube);
	//Render my scene
	render();
}
var moveDom = function(){
    vector = geometry.vertices[0].clone();
    vector.applyMatrix4(cube.matrix);
    vector.project(camera);
	  vector.x = (vector.x * ww/2) + ww/2;
	  vector.y = -(vector.y * wh/2) + wh/2;

    //Get the DOM element and apply transforms on it
   	document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
   	document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};


var counter = 0;
var render = function (a) {
	requestAnimationFrame(render);

	counter++;

	//Move my cubes
		cube.position.x = Math.cos((counter+1*150)/200)*(ww/6+1*80);
		cube.position.y = Math.sin((counter+1*150)/200)*(70+1*80);
		cube.rotation.x += .001*1+.002;
		cube.rotation.y += .001*1+.02;

		//Move my dom elements
		moveDom();
	
	renderer.render(scene, camera);
};

init();
body,html, canvas{width:100%;height:100%;padding:0;margin:0;overflow: hidden;}
.element{color:white;position:absolute;top:0;left:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<!-- My scene -->
	<canvas id="scene"></canvas>
	<div class="element">
    <h1>Not Earth</h1>
	</div>

If you downvote this, please tell me why. I will try my best to improve my posts.


If you are using spriteMaterial to present your text, you could try to set the sizeAttenuation attribute to false.

var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff, sizeAttenuation:false } );

See more information from here: https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation