元素childNodes的Javascript Typewriter效果

时间:2019-04-09 16:16:46

标签: javascript jquery html

我正在尝试创建一个类型编写器效果,该效果将获取元素的节点,然后以给定的速度顺序显示这些节点的值。如果该节点是文本节点,则希望它进入并按顺序显示该文本中的每个字符。

HTML:

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<!-- item will be appened to this layout -->
<div id="log" class="sl__chat__layout">
</div>


<!-- chat item -->
<script type="text/template" id="chatlist_item">

  <div data-from="{from}" data-id="{messageId}" id="messageID">
    <div id="messageBox">

    <span id="message">
      {message}
    </span>

    </div>
  </div>
</script>

Javascript:

// Please use event listeners to run functions.
document.addEventListener('onLoad', function(obj) {
    // obj will be empty for chat widget
    // this will fire only once when the widget loads
});

document.addEventListener('onEventReceived', function(obj) {
    // obj will contain information about the event
e++  
typeEffect(e);  
});

var speed = 50;
var e = 1;

function typeEffect(inp) {
    var o = inp;
    document.getElementById("messageID").id= "messageID"+o;
    document.getElementById("message").id= "message"+o;
    var text = $("#message"+o).text();
    $("#message"+o).text('');

    var i = 0;
    var timer = setInterval(function() {
        if(i < text.length) {
            $("#message"+o).append(text.charAt(i));
            i++;
        }

        else{
            clearInterval(timer);
        };   
  }, speed);

    }                    

这里是id为“ message2”的元素的示例。如您所见,它包含一些文本,然后是包含图像的跨度,然后是更多文本。

   <span id="message2">
      Hello 
      <span class="emote">
         <img src="https://static-cdn.jtvnw.net/emoticons/v1/1251411/1.0"> 
      </span> 
      There
    </span>

在上面发布的代码中,我可以创建文本的打字机效果。但是,使用上面的示例,我无法找出一种方法来键入“ Hello”,然后键入带有图像的跨度,然后键入“ There”。

我试图获得这样的节点:

   var contents = document.getElementById("message"+o).childNodes;

当我将其登录到控制台时,我得到:NodeList(3)[文本,span.emote,文本]

但是从那里我无法访问nodeValues。我不断出错。我不确定自己在做什么错。从那里我也不确定清空“ message” + o元素然后重新填充信息的正确方法。

希望一切都能解释!

1 个答案:

答案 0 :(得分:0)

通过使用$.text(),您将获得Element的textContent,并且其所有标记内容均消失了(实际上是其所有子代)。

为了保留此内容,您需要存储DOM节点而不是仅存储它们的textContent
从那里,您将必须分离DOM树并在附加每个Element的同时对其进行遍历,并在每个TextNode的textContent上缓慢地进行迭代。

但是,这样做并不容易。确实,我们将在文档内部重新添加DOM节点这一事实意味着分离的DOM树 我们走路时会摔碎的。

要避免这种情况,我们需要创建一个分离的DOM树的副本,并保持其完整无缺,因此我们可以像对待原始DOM树一样继续对其进行遍历。 为了知道元素的放置位置,我们需要将每个原始节点存储为克隆节点的属性。

为此,我们将创建两个TreeWalkers,一个用于原始节点,一个用于克隆版本。通过同时漫游两个对象,我们可以轻松设置克隆的.original属性。
然后,我们只需要回到克隆TreeWalker的根目录并再次开始遍历它,这一次便能够将正确的节点附加到其原始parentNode中。

async function typeWrite(root, freq) {
  // grab our element's content
  const content = [...root.childNodes];
  // move it to a documentFragment
  const originals = document.createDocumentFragment();
  originals.append.apply(originals, content);
  // clone this documentFragment so can keep a clean version of the DOM tree
  const clones = originals.cloneNode(true);
  // every clone will have an `original` node
  // clones documentFragment's one is the root Element, still in doc
  clones.original = root;
  
  // make two TreeWalkers
  const originals_walker = document.createTreeWalker(originals, NodeFilter.SHOW_ALL, null);
  const clones_walker = document.createTreeWalker(clones, NodeFilter.SHOW_ALL, null);

  while(originals_walker.nextNode() && clones_walker.nextNode()) {
    // link each original node to its clone
    clones_walker.currentNode.original = 
      originals_walker.currentNode
  }
  while(clones_walker.parentNode()) {
    // go back to root
  }
  // walk down only our clones (will stay untouched now)
  while(clones_walker.nextNode()) {
  
    const clone = clones_walker.currentNode;
    const original = clone.original;
    // retrieve the original parentNode (which is already in doc)
    clone.parentNode.original
      .append(original); // and append the original version of our currentNode
      
    if(clone.nodeType === 3) { // TextNode
      const originalText = original.textContent;
      // we use a trimmed version to avoid all non visible characters
      const txt = originalText.trim().replace(/\n/g, '');
      original.textContent = ''; // in doc => empty for now
      let i = 0;
      while(i < txt.length) {
        await wait(freq); // TypeWriting effect...
        original.textContent += txt[i++];
      }
      // restore original textContent (invisible to user)
      original.textContent = originalText;
    }
  }
}

typeWrite(message2, 200)
  .catch(console.error);

function wait(time) {
  return new Promise(res => setTimeout(res, time));
}
<span id="message2">
  Hello 
  <span class="emote">
     <img src="https://static-cdn.jtvnw.net/emoticons/v1/1251411/1.0"> 
  </span> 
  There
</span>