egig
egig

Reputation: 4430

Create simple typing effect

function say(str){
    for(i=0;i<str.length;i++){
        setTimeout(function(){
            $("#text").append(str[i]);
            console.log(str[i]);
        },
        i*200);
    }
}

say("Hello World !");

Look: http://jsfiddle.net/XnKNX/

It seems like each str[i] is undefined;

How to get it works ? I am learning, so it'll be great if somebody explain things. Thanks.

Upvotes: 1

Views: 502

Answers (3)

F&#225;bio Junqueira
F&#225;bio Junqueira

Reputation: 2781

I stumbled upon this question when looking for a script to use on my website. After looking for a while I decided to write my own.

Here is the link for it on Github, hopefully it might help someone :)

A brief explanation

class Typer {

    constructor(typingSpeed, content, output) {

        this.typingSpeed = typingSpeed;
        // Parses a NodeList to a series of chained promises
        this.parseHtml(Array.from(content), output);
    };

    makePromise(node, output) {

        if (node.nodeType == 1) // element 
        {
            // When a new html tag is detected, append it to the document
            return new Promise((resolve) => {
                var tag = $(node.outerHTML.replace(node.innerHTML, ""));
                tag.appendTo(output);
                resolve(tag);
            });

        } else if (node.nodeType == 3) // text
        {
            // When text is detected, create a promise that appends a character
            // and sleeps for a while before adding the next one, and so on...
            return this.type(node, output, 0);
        } else {
            console.warn("Unknown node type");
        }
    }

    parseHtml(nodes, output) {
        return nodes.reduce((previous, current) => previous
            .then(() => this.makePromise(current, output)
                .then((output) => this.parseHtml(Array.from(current.childNodes), output))), Promise.resolve());
    }

    type(node, output, textPosition) {
        var textIncrement = textPosition + 1;

        var substring = node.data.substring(textPosition, textIncrement);

        if (substring !== "") {
            return new Promise(resolve => setTimeout(resolve, this.typingSpeed))
                .then(() => output.append(substring))
                .then(() => this.type(node, output, textIncrement));
        }

        return Promise.resolve(output);
    }
}

Upvotes: 1

Ian
Ian

Reputation: 50905

I wouldn't suggest using a for loop and trying to get the actual timing to line up. Recursively calling a function with setTimeout makes more sense to me, and doesn't suffer from the closure problem you have. You can use:

function say(str) {
    (function iterator(index) {
        if (index < str.length) {
            $("#text").append(str.charAt(index));
            console.log(str.charAt(index));
            setTimeout(function () {
                iterator(++index);
            }, 200);
        }
    })(0);
}
say("Hello World !");

DEMO: http://jsfiddle.net/XnKNX/6/

.charAt() is the preferred way of getting a string's character because older IE doesn't support [] indexing.

If you wanted to keep the for loop (for whatever reason), you could set it up like this:

var say = (function () {
    var generateTyper = function (s, index) {
        return function () {
            $("#text").append(s.charAt(index));
            console.log(s.charAt(index));
        };
    };
    return function (str) {
        for (var i = 0, j = str.length; i < j; i++) {
            setTimeout(generateTyper(str, i), i * 200);
        }
    };
})();
say("Hello World !");

DEMO: http://jsfiddle.net/8kxFd/1/

Upvotes: 2

Denys S&#233;guret
Denys S&#233;guret

Reputation: 382130

That's because i has the value of end of loop when the callback you give to setTimeout is called.

A solution is to change it to

function say(str){
    for(i=0;i<str.length;i++){
        (function(i){
          setTimeout(function(){
            $("#text").append(str[i]);
          },
          i*200);
        })(i);
    }
}

This saves in the immediately called internal function the value of i at iteration time.

Upvotes: 3

Related Questions