How to make TextGeometry in THREE JS follow mouse?

Viewed 1013

This is my source code. I am trying to make the text rotate according to mouse position.

// Initialization
            const scene = new THREE.Scene();
      let camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
      let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            let body = document.getElementsByTagName("body");
            let pageX = 0.5;
            let pageY = 0.5;

      renderer.setSize( window.innerWidth, window.innerHeight );

      document.getElementById("board").appendChild(renderer.domElement);

      // Handle resize event
      window.addEventListener('resize', () => {
        renderer.setSize( window.innerWidth, window.innerHeight );
        camera.aspect = window.innerWidth / window.innerHeight;

        camera.updateProjectionMatrix();
      });

      camera.position.z = 20;

            // Create light
            let directLight = new THREE.DirectionalLight('#fff', 4);
            directLight.position.set(0, 7, 5);
            scene.add(directLight);

            var light = new THREE.AmbientLight( 0x404040 ); // soft white light
            scene.add( light );

            function animate (){
              requestAnimationFrame( animate );
                var loader = new THREE.FontLoader();
                loader.load( 'https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', function ( font ) {
                    var geometry = new THREE.TextGeometry( 'Hello three.js!', {
                        font: font,
                        size: 3,
                        height: 0.5,
                        curveSegments: 4,
                        bevelEnabled: true,
                        bevelThickness: 0.02,
                        bevelSize: 0.05,
                        bevelSegments: 3
                    } );
                    geometry.center();
                    var material = new THREE.MeshPhongMaterial(
                        { color: '#dbe4eb', specular: '#dbe4eb' }
                    );
                    var mesh = new THREE.Mesh( geometry, material );

                    mesh.rotation.x = (pageY - 0.5) * 2;
                    mesh.rotation.y = (pageX - 0.5) * 2;

                    scene.add( mesh );
                } );
        renderer.render(scene, camera);
      }

      animate();

            // Get mouse coordinates inside the browser
            document.body.addEventListener('mousemove', (event) => {

                pageX = event.pageX / window.innerWidth;
                pageY = event.pageY / window.innerHeight;

            });
      renderer.render(scene, camera);

        </script>

This is the best I could get. The problem is that each time I move the mouse, it instantiates a new mesh and rotates it accordingly, and I only need one mesh to follow the mouse. Can anyone help? Thanks in advance!

1 Answers

As you already figured out, each frame you're reloading the font and ultimately recreating the mesh each time.

To get around this you need to move the font loading and object creation inside some initialization function, so it just happens once.

The only part of code you want to keep inside the render loop is the updating of the text's rotation according to the mouse movement:

mesh.rotation.x = (pageY - 0.5) * 2;
mesh.rotation.y = (pageX - 0.5) * 2;

This will bring up another problem though. Since mesh is a local object defined inside the callback function of the font loader, it won't be accessible outside. Luckily three.js offers a property called .name which you can use to give your object a name. e.g.

var mesh = new THREE.Mesh(geometry, material);
mesh.name = "myText";
scene.add(mesh);

Later on you can get a reference to this object using:

scene.getObjectByName("myText")

Here's an example:

var container, scene, camera, renderer, pageX, pageY;

function init() {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  pageX = 0.5;
  pageY = 0.5;

  renderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById("container").appendChild(renderer.domElement);

  window.addEventListener('resize', () => {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;

    camera.updateProjectionMatrix();
  });

  camera.position.z = 20;

  let directLight = new THREE.DirectionalLight('#fff', 4);
  directLight.position.set(0, 7, 5);
  scene.add(directLight);

  var light = new THREE.AmbientLight(0x404040); // soft white light
  scene.add(light);

  var loader = new THREE.FontLoader();
  loader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', function(font) {

    var geometry = new THREE.TextGeometry('Hello three.js!', {
      font: font,
      size: 3,
      height: 0.5,
      curveSegments: 4,
      bevelEnabled: true,
      bevelThickness: 0.02,
      bevelSize: 0.05,
      bevelSegments: 3
    });
    geometry.center();
    var material = new THREE.MeshPhongMaterial({
      color: '#dbe4eb',
      specular: '#dbe4eb'
    });
    var mesh = new THREE.Mesh(geometry, material);
    mesh.name = "myText";


    scene.add(mesh);
    animate();
  });

  document.body.addEventListener('mousemove', (event) => {
    pageX = event.pageX / window.innerWidth;
    pageY = event.pageY / window.innerHeight;
  });
}

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

function render() {
  scene.getObjectByName("myText").rotation.x = (pageY - 0.5) * 2;
  scene.getObjectByName("myText").rotation.y = (pageX - 0.5) * 2;
  renderer.render(scene, camera);
}

init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<div id="container"></div>

Related