Is there any way for animating the text (atext) in aframe [WebAR]?

388 Views Asked by At

In Web AR,I need to animate atext component in aframe. How to achieve the text animation in aframe ? Not found any properties in atext component.

1

There are 1 best solutions below

6
On BEST ANSWER

As far as I know, there is no simple way to treat the letters as separate entities. They're even not separate meshes - the component generates one geometry containing all letters.

Probably it would be better to create an animated model, or even to use an animated texture.


However, with a little bit javascript we can dig into the underlying THREE.js layer and split a text into separate letters.

One way would be to attach text components with a single letters to <a-entity> nodes.

Having <a-entity> nodes declared we can attach animations like we would to a normal a-frame entity (especially position / rotation ones). But there comes the issue of positioning:

<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
  AFRAME.registerComponent("splitter", {
    init: function() {
      // grab all <a-entity> letter nodes
      const letter_nodes = document.querySelectorAll(".letter");
      // grab the "text" configuration
      const textData = this.el.getAttribute("text");
      // create a temporary vector to store the position
      const vec = new THREE.Vector3();
      
      for (var i = 0; i < letter_nodes.length; i++) {
        // set the "text" component in each letter node
        letter_nodes[i].setAttribute('text', {
          value: textData.value[i],
          anchor: textData.align, // a-text binding
          width: textData.width // a-text binding
        })
        // set position
        vec.copy(this.el.getAttribute("position"));
        vec.x += i * 0.1; // move the letters to the right
        vec.y -= 0.2; // move them down
        letter_nodes[i].setAttribute("position", vec)
      }
    }
  })
</script>
<a-scene>
  <!-- original text -->
  <a-text value="foo" position="0 1.6 -1" splitter></a-text>

  <!-- entities  -->
  <a-entity class="letter" animation="property: position; to: 0 1.2 -1; dur: 1000; dir: alternate; loop: true"></a-entity>
  <a-entity class="letter" animation="property: rotation; to: 0 0 360; dur: 1000; dir: alternate; loop: true"></a-entity>
  <a-entity class="letter"></a-entity>
  <a-sky color="#ECECEC"></a-sky>
</a-scene>

First of all - we need to get the original letters spacing, this is sloppy. From what I can tell, a-frames version of THREE.TextGeometry has a property visibleGlyphs, which has the position of the glyphs (as well as their heights, and offsets). We can use it to properly position our text.

Second of all - the position animation needs the global position. It would be better to input offsets, not target positions. To make it work, the text nodes could be child nodes of the .letter nodes.

A "generic" component could look like this:

<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
  AFRAME.registerComponent("text-splitter", {
    init: function() {
      const el = this.el;
      // i'll use the child nodes as wrapper entities for letter entities
      const letter_wrappers = el.children

      // wait until the text component tells us that it's ready
      this.el.addEventListener("object3dset", function objectset(evt) {
        el.removeEventListener("object3dset", objectset); // react only once

        const mesh = el.getObject3D("text") // grab the mesh
        const geometry = mesh.geometry // grab the text geometry

        // wait until the visibleGlyphs are set
        const idx = setInterval(evt => {
          if (!geometry.visibleGlyphs) return;
          clearInterval(idx);

          // we want data.height, data.yoffset and position from each glyph
          const glyphs = geometry.visibleGlyphs

          // do as many loops as there are <entity - glyph> pairs
          const iterations = Math.min(letter_wrappers.length, glyphs.length)
          const textData = el.getAttribute("text"); // original configuration
          var text = textData.value.replace(/\s+/, "") // get rid of spaces

          const letter_pos = new THREE.Vector3();
          for (var i = 0; i < iterations; i++) {
            // use the positions, heights, and offsets of the glyphs
            letter_pos.set(glyphs[i].position[0], glyphs[i].position[1], 0);
            letter_pos.y += (glyphs[i].data.height + glyphs[i].data.yoffset) / 2;

            // convert the letter local position to world
            mesh.localToWorld(letter_pos)

            // convert the world position to the <a-text> position
            el.object3D.worldToLocal(letter_pos)

            // apply the text and position to the wrappers
            const node = document.createElement("a-entity")
            node.setAttribute("position", letter_pos)
            node.setAttribute('text', {
              value: text[i],
              anchor: textData.align, // a-text binding
              width: textData.width // a-text binding
            })
            letter_wrappers[i].appendChild(node)
          }
          // remove the original text
          el.removeAttribute("text")
        }, 100)
      })
    }
  })

</script>
<a-scene>
  <!-- child entities of the original a-text are used as letter wrappers -->
  <a-text value="fo o" position="0 1.6 -1" text-splitter>
    <a-entity animation="property: rotation; to: 0 0 360; dur: 1000; loop: true"></a-entity>
    <a-entity animation="property: position; to: 0 0.25 0; dur: 500; dir: alternate; loop: true"></a-entity>
    <a-entity animation="property: position; to: 0 -0.25 0; dur: 500; dir: alternate; loop: true"></a-entity>
  </a-text>
  
  <!-- just to see that the text is aligned properly-->
  <a-text value="fo o" position="0 1.6 -1"></a-text>
  <a-sky color="#ECECEC"></a-sky>
</a-scene>


Here's an example of dynamically adding text entities + using anime.js to set up a timeline for each letter.