Updating FontLoader difficulties

Hello all!
I am trying to update this threejs version r128 to 141 (rest of the animations on page are 0.141) but am struggling to get this code to work. I know for sure there is an issue with how I am importing FontLoader since the change in patch 132–>133 "FontLoader and Font have been removed from core. Both classes are now located in examples/jsm/loaders/FontLoader.js "

But the docs page for FontLoader on official ThreeJs website is updated to 0.148 and doesn’t show info on older versions as it is now pointing at /addons/ folder:
three.js docs

  • so I have no idea how to solve this or use FontLoader for 141. I tried many many many many different ways and could not get it to work. I tried local npm install three@0.141 and manually calling the local location of FontLoader into the script and it doesn’t work. Here is the .js, which currently functions under r128:

const preload = () => {
    let manager = new THREE.LoadingManager();
    manager.onLoad = function () {
      const environment = new Environment(typo, particle);
    };
  
    var typo = null;
    const loader = new THREE.FontLoader(manager);
    const font = loader.load(
      "https://res.cloudinary.com/dydre7amr/raw/upload/v1612950355/font_zsd4dr.json",
      function (font) {
        typo = font;
      }
    );
    const particle = new THREE.TextureLoader(manager).load(
      "https://res.cloudinary.com/dfvtkoboz/image/upload/v1605013866/particle_a64uzf.png"
    );
  };
  
  if (
    document.readyState === "complete" ||
    (document.readyState !== "loading" && !document.documentElement.doScroll)
  )
    preload();
  else document.addEventListener("DOMContentLoaded", preload);
  
  let magicDivInViewport = false; // Track if the magic div is in the viewport
  
  class Environment {
    constructor(font, particle) {
      this.font = font;
      this.particle = particle;
      this.container = document.querySelector("#magic");
      this.scene = new THREE.Scene();
      this.createCamera();
      this.createRenderer();
      this.setup();
      this.bindEvents();
      this.startTimer();
    }
  
    startTimer() {
      this.mouseEnabled = false;
      this.createParticles.onMouseDown();
      this.createParticles.disableMouseTracking(); // Disable mouse tracking
  
      setTimeout(() => {
        this.createParticles.onMouseUp();
        this.createParticles.enableMouseTracking(); // Enable mouse tracking
        this.mouseEnabled = true;
      }, 6000);
    }
  
    bindEvents() {
      window.addEventListener("resize", this.onWindowResize.bind(this));
      window.addEventListener("scroll", this.checkMagicDivInViewport.bind(this));
    }
  
    checkMagicDivInViewport() {
      const magicDiv = document.querySelector("#magic");
      const rect = magicDiv.getBoundingClientRect();
      magicDivInViewport = rect.top <= window.innerHeight && rect.bottom >= 0;
      
      if (!magicDivInViewport) {
        // Magic div is out of view, stop JavaScript associated with it
        window.removeEventListener("scroll", this.checkMagicDivInViewport.bind(this));
        // Remove other event listeners and clean up resources as needed
      }
    }
  
    setup() {
      this.createParticles = new CreateParticles(
        this.scene,
        this.font,
        this.particle,
        this.camera,
        this.renderer
      );
    }
  
    render() {
      this.createParticles.render();
      this.renderer.render(this.scene, this.camera);
    }
  
    createCamera() {
      this.camera = new THREE.PerspectiveCamera(
        65,
        this.container.clientWidth / this.container.clientHeight,
        1,
        10000
      );
      this.camera.position.set(0, 0, 100);
    }
  
    createRenderer() {
      this.renderer = new THREE.WebGLRenderer();
      this.renderer.setSize(
        this.container.clientWidth,
        this.container.clientHeight
      );
  
      this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.container.appendChild(this.renderer.domElement);
  
      this.renderer.setAnimationLoop(() => {
        this.render();
      });
    }
  
    onWindowResize() {
      this.camera.aspect =
        this.container.clientWidth / this.container.clientHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(
        this.container.clientWidth,
        this.container.clientHeight
      );
    }
  }
  
  class CreateParticles {
      constructor(scene, font, particleImg, camera, renderer) {
          this.scene = scene;
          this.font = font;
          this.particleImg = particleImg;
          this.camera = camera;
          this.renderer = renderer;
  
          this.raycaster = new THREE.Raycaster();
          this.mouse = new THREE.Vector2(-200, 200);
  
          this.colorChange = new THREE.Color();
          this.mouseEnabled = false;  // Initialize as false.
          this.mouseTrackingEnabled = false; // Initialize as false.
  
          this.button = false;
  
          this.data = {
              text: "FUTURE\nIS NOW",
              amount: 1500,
              particleSize: 1,
              particleColor: 0xffffff,
              textSize: 16,
              area: 250,
              ease: 0.05
          };
  
          this.setup();
          this.bindEvents();
      }
  
     bindEvents() {
      window.addEventListener("resize", this.onWindowResize.bind(this));
  
      setTimeout(() => {
        this.mouseEnabled = true; // Enable after 3 seconds
        this.mouseTrackingEnabled = true; // Enable after 3 seconds
  
        // Attach mouse event listeners after the delay
        document.addEventListener("mousedown", this.onMouseDown.bind(this));
        document.addEventListener("mousemove", this.onMouseMove.bind(this));
        document.addEventListener("mouseup", this.onMouseUp.bind(this));
      }, 6000); // 3 seconds delay
    }
  
  
      disableMouseTracking() {
          this.mouseTrackingEnabled = false;
      }
  
      enableMouseTracking() {
          this.mouseTrackingEnabled = true;
      }
  
  
  
  
  
    setup() {
      const geometry = new THREE.PlaneGeometry(
        this.visibleWidthAtZDepth(100, this.camera),
        this.visibleHeightAtZDepth(100, this.camera)
      );
      const material = new THREE.MeshBasicMaterial({
        color: 0x00ff00,
        transparent: true
      });
      this.planeArea = new THREE.Mesh(geometry, material);
      this.planeArea.visible = false;
      this.createText();
    }
  
    bindEvents() {
          this.mouseEnabled = false; // Initially set this to false
  
      setTimeout(() => {
          this.mouseEnabled = true; // Enable after 6 seconds
      }, 6000); // 6000 milliseconds equals 6 seconds
      document.addEventListener("mousedown", this.onMouseDown.bind(this));
      document.addEventListener("mousemove", this.onMouseMove.bind(this));
      document.addEventListener("mouseup", this.onMouseUp.bind(this));
    }
  
      onMouseDown(event) {
      // Check if the event is undefined, which means it's a simulated mouse down
      if (!event) {
        // Simulate mouse down at the center of the window
        this.mouse.x = 0;
        this.mouse.y = 0;
      } else {
        this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      }
  
      const vector = new THREE.Vector3(this.mouse.x, this.mouse.y, 0.5);
      vector.unproject(this.camera);
      const dir = vector.sub(this.camera.position).normalize();
      const distance = -this.camera.position.z / dir.z;
      this.currenPosition = this.camera.position
        .clone()
        .add(dir.multiplyScalar(distance));
  
      const pos = this.particles.geometry.attributes.position;
      this.buttom = true;
      this.data.ease = 0.01;
    }
    onMouseUp() {
      this.buttom = false;
      this.data.ease = 0.05;
    }
  
    onMouseMove() {
      this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    }
  
    render(level) {
      const time = ((0.001 * performance.now()) % 12) / 12;
      const zigzagTime = (1 + Math.sin(time * 2 * Math.PI)) / 6;
  
      this.raycaster.setFromCamera(this.mouse, this.camera);
  
      const intersects = this.raycaster.intersectObject(this.planeArea);
  
      if (intersects.length > 0) {
        const pos = this.particles.geometry.attributes.position;
        const copy = this.geometryCopy.attributes.position;
        const coulors = this.particles.geometry.attributes.customColor;
        const size = this.particles.geometry.attributes.size;
  
        const mx = intersects[0].point.x;
        const my = intersects[0].point.y;
        const mz = intersects[0].point.z;
  
        for (var i = 0, l = pos.count; i < l; i++) {
          const initX = copy.getX(i);
          const initY = copy.getY(i);
          const initZ = copy.getZ(i);
  
          let px = pos.getX(i);
          let py = pos.getY(i);
          let pz = pos.getZ(i);
  
          this.colorChange.setHSL(0.5, 1, 1);
          coulors.setXYZ(
            i,
            this.colorChange.r,
            this.colorChange.g,
            this.colorChange.b
          );
          coulors.needsUpdate = true;
  
          size.array[i] = this.data.particleSize;
          size.needsUpdate = true;
  
          let dx = mx - px;
          let dy = my - py;
          const dz = mz - pz;
  
          const mouseDistance = this.distance(mx, my, px, py);
          let d = (dx = mx - px) * dx + (dy = my - py) * dy;
          const f = -this.data.area / d;
  
          if (this.buttom) {
            const t = Math.atan2(dy, dx);
            px -= f * Math.cos(t);
            py -= f * Math.sin(t);
  
            this.colorChange.setHSL(0.5 + zigzagTime, 1.0, 0.5);
            coulors.setXYZ(
              i,
              this.colorChange.r,
              this.colorChange.g,
              this.colorChange.b
            );
            coulors.needsUpdate = true;
  
            if (
              px > initX + 70 ||
              px < initX - 70 ||
              py > initY + 70 ||
              py < initY - 70
            ) {
              this.colorChange.setHSL(0.15, 1.0, 0.5);
              coulors.setXYZ(
                i,
                this.colorChange.r,
                this.colorChange.g,
                this.colorChange.b
              );
              coulors.needsUpdate = true;
            }
          } else {
            if (mouseDistance < this.data.area) {
              if (i % 5 == 0) {
                const t = Math.atan2(dy, dx);
                px -= 0.03 * Math.cos(t);
                py -= 0.03 * Math.sin(t);
  
                this.colorChange.setHSL(0.15, 1.0, 0.5);
                coulors.setXYZ(
                  i,
                  this.colorChange.r,
                  this.colorChange.g,
                  this.colorChange.b
                );
                coulors.needsUpdate = true;
  
                size.array[i] = this.data.particleSize / 1.2;
                size.needsUpdate = true;
              } else {
                const t = Math.atan2(dy, dx);
                px += f * Math.cos(t);
                py += f * Math.sin(t);
  
                pos.setXYZ(i, px, py, pz);
                pos.needsUpdate = true;
  
                size.array[i] = this.data.particleSize * 1.3;
                size.needsUpdate = true;
              }
  
              if (
                px > initX + 10 ||
                px < initX - 10 ||
                py > initY + 10 ||
                py < initY - 10
              ) {
                this.colorChange.setHSL(0.15, 1.0, 0.5);
                coulors.setXYZ(
                  i,
                  this.colorChange.r,
                  this.colorChange.g,
                  this.colorChange.b
                );
                coulors.needsUpdate = true;
  
                size.array[i] = this.data.particleSize / 1.8;
                size.needsUpdate = true;
              }
            }
          }
  
          px += (initX - px) * this.data.ease;
          py += (initY - py) * this.data.ease;
          pz += (initZ - pz) * this.data.ease;
  
          pos.setXYZ(i, px, py, pz);
          pos.needsUpdate = true;
        }
      }
    }
  
    createText() {
      let thePoints = [];
  
      let shapes = this.font.generateShapes(this.data.text, this.data.textSize);
      let geometry = new THREE.ShapeGeometry(shapes);
      geometry.computeBoundingBox();
  
      const xMid =
        -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
      const yMid =
        (geometry.boundingBox.max.y - geometry.boundingBox.min.y) / 2.85;
  
      geometry.center();
  
      let holeShapes = [];
  
      for (let q = 0; q < shapes.length; q++) {
        let shape = shapes[q];
  
        if (shape.holes && shape.holes.length > 0) {
          for (let j = 0; j < shape.holes.length; j++) {
            let hole = shape.holes[j];
            holeShapes.push(hole);
          }
        }
      }
      shapes.push.apply(shapes, holeShapes);
  
      let colors = [];
      let sizes = [];
  
      for (let x = 0; x < shapes.length; x++) {
        let shape = shapes[x];
  
        const amountPoints =
          shape.type == "Path" ? this.data.amount / 2 : this.data.amount;
  
        let points = shape.getSpacedPoints(amountPoints);
  
        points.forEach((element, z) => {
          const a = new THREE.Vector3(element.x, element.y, 0);
          thePoints.push(a);
          colors.push(this.colorChange.r, this.colorChange.g, this.colorChange.b);
          sizes.push(1);
        });
      }
  
      let geoParticles = new THREE.BufferGeometry().setFromPoints(thePoints);
      geoParticles.translate(xMid, yMid, 0);
  
      geoParticles.setAttribute(
        "customColor",
        new THREE.Float32BufferAttribute(colors, 3)
      );
      geoParticles.setAttribute(
        "size",
        new THREE.Float32BufferAttribute(sizes, 1)
      );
  
      const material = new THREE.ShaderMaterial({
        uniforms: {
          color: { value: new THREE.Color(0xffffff) },
          pointTexture: { value: this.particleImg }
        },
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
  
        blending: THREE.AdditiveBlending,
        depthTest: false,
        transparent: true
      });
  
      this.particles = new THREE.Points(geoParticles, material);
      this.scene.add(this.particles);
  
      this.geometryCopy = new THREE.BufferGeometry();
      this.geometryCopy.copy(this.particles.geometry);
    }
  
    visibleHeightAtZDepth(depth, camera) {
      const cameraOffset = camera.position.z;
      if (depth < cameraOffset) depth -= cameraOffset;
      else depth += cameraOffset;
  
      const vFOV = (camera.fov * Math.PI) / 180;
  
      return 2 * Math.tan(vFOV / 2) * Math.abs(depth);
    }
  
    visibleWidthAtZDepth(depth, camera) {
      const height = this.visibleHeightAtZDepth(depth, camera);
      return height * camera.aspect;
    }
  
    distance(x1, y1, x2, y2) {
      return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
    }
  }
  // Shaders
const vertexShader = `
attribute float size;
attribute vec3 customColor;
varying vec3 vColor;

void main() {
  vColor = customColor;
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
  gl_PointSize = size * ( 300.0 / -mvPosition.z );
  gl_Position = projectionMatrix * mvPosition;
}`;

const fragmentShader = `
uniform vec3 color;
uniform sampler2D pointTexture;
varying vec3 vColor;

void main() {
  gl_FragColor = vec4( color * vColor, 1.0 );
  gl_FragColor = gl_FragColor * texture2D( pointTexture, gl_PointCoord );
}`;

When you import FontLoader via ES6 import syntax, the THREE namespace is not required anymore. Since you are using npm, the import itself should look like so:

import { FontLoader } from 'three/addons/loaders/FontLoader.js';

It should be sufficient to change these two things.

Thank you for responding.
I notice you provided a solution for version 0.148+ but I am specifically looking for a solution for 0.141 where FontLoader is located at
https://threejs.org/examples/jsm/loaders/FontLoader.js
I have my current script in CodePen to experiment, you can see it functioning under r128 here:

I have two major animations that are bundled and implemented with 20,000+ lines of code each that use 0.141 so it makes sense to just get this 128 updated to 141 instead of going through all three and trying to manually update to 148+, I know its possible and probably a simple solution - I just cannot find any documentation online for 141’s syntax. The update with FontLoader happened in 132–>133.

Here is my current attempt to update to 0.141 in this non-functioning CodePen:

The problem in your codepen is that include three.min.js as a global script but use FontLoader as an ES6 module. Mixing global scripts with ES6 modules is incompatible.

I’ve updated your code so it is compatible with r141. Try it with: https://jsfiddle.net/scrzLukd/3/

BTW: Nice effect!

you freakin did it. You have no idea how long I’ve been stuck on this, over 40 hours lol
thank you!

1 Like