简体   繁体   中英

Javascript Typewriter Effect from element childNodes

I am trying to create a type writer effect that will get the nodes of an element and then display the values of those nodes sequentially at a given speed. If the node is a text node I want it to go in and sequentially display each character in that text.

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

    }                    

Here is an example of an element with the id "message2". As you can see it contains some text, then a span containing an image and then some more text.

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

In my code posted above I am able to create the typewriter effect of the text. However, using the above example, I can't figure out a way to type "Hello" then the span with the image and then "There".

I have tried to get the nodes like this:

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

When I log that to the console I get: NodeList(3) [text, span.emote, text]

From there however I am having trouble accessing the nodeValues. I keep getting errors thrown. I am not sure exactly what I am doing wrong. From there I am also not sure the proper way to empty the "message"+o element and then refill it with the information.

Hopefully that explains everything!

By using $.text() , you are getting your Element's textContent , and all its markup content is gone (actually all its children).

In order to retain this content, you need to store the DOM nodes instead of just their textContent .
From there, you will have to detach the DOM tree and walk it while appending every Element, iterating slowly over each TextNode's textContent .

However, doing so is not that easy. Indeed, the fact that we will re-append the DOM nodes inside the document means that the detached DOM tree we were walking will get broken.

To circumvent that, we thus need to create a copy of the detached DOM tree, that we will keep intact, so we can continue walking it just like if it were the original one.
And in order to know where to place our elements, we need to store each original node as a property of the cloned one.

To do so, we'll create two TreeWalkers , one for the original nodes, and one for the cloned version. By walking both at the same time, we can set our clones' .original property easily.
We then just have to go back to the root of our clones TreeWalker and start again walking it, this time being able to append the correct node to its original 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM