2

在 Web AR 中,我需要为框架中的文本组件设置动画。如何实现aframe中的文字动画?在文本组件中找不到任何属性。

4

1 回答 1

1

据我所知,没有简单的方法将字母视为单独的实体。它们甚至不是单独的网格——组件生成一个包含所有字母的几何图形。

可能最好创建一个动画模型,甚至使用动画纹理。


但是,使用一点 javascript,我们可以深入底层THREE.js并将 a 拆分text为单独的字母。

一种方法是将text带有单个字母的组件附加到<a-entity>节点。

声明<a-entity>节点后,我们可以将动画附加到普通a-frame实体(尤其是位置/旋转实体)。但随之而来的是定位问题:

<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>

首先 - 我们需要获取原始字母间距,这很草率。据我所知,a-frames 版本THREE.TextGeometry有一个属性visibleGlyphs,它具有字形的位置(以及它们的高度和偏移量)。我们可以使用它来正确定位我们的文本。

其次 - 位置动画需要全局位置。最好输入偏移量,而不是目标位置。为了使其工作,文本节点可以是节点的子.letter节点。

“通用”组件可能如下所示:

<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>


这是一个动态添加文本实体的示例 + 使用anime.js 为每个字母设置时间线。

于 2022-01-11T23:13:54.553 回答