user3871
user3871

Reputation: 12718

Simulating text typing effect in JavaScript without setTimeout

I'm making a RPG and would like the dialog displayed in a typing effect (letter by letter typed onto the dialog box). I've run into a recursion issue with setTimeout(), as I've asked here:

How to ensure a recursive function isn't called again before it returns

A SO user recommended "avoid using a string in setTimeout... that should have stopped being taught years and years ago; performance-killer, safety-hazard if you're mashing in user-created strings, easy to make non-strict eval-context mistakes."

So if I shouldn't be using setTimeout(), how else can I add delay to each character of a string being typed onto the screen?

One way I could think of is to set a variable counter to x, and decrement it (let's say, set i = 1000, then loop through until i = 0, then recur again... until string has been fully typed) But would this be any faster? Would it solve the issues? It also doesn't give me a good sense of how long it will take to decrement. In setTimeout(), you can specify milliseconds.

So far, this is how I'm designing the typeEffect (it types fine, but I can keep hitting the type button which keeps re-running the script).

typeEffect : function (index) {
    $("#responseButton").prop('disabled', true); //wait until typing is done before user can press again
    game.data.enableCycle = false;

    setTimeout(function () {
        $("#npc_dialog").append(game.data.NPCdialog.charAt(index));
        index++;
        if (index < game.data.NPCdialog.length) {
            Utilities.typeEffect(index);
        }
        else {
            game.data.enableCycle = true;
            Utilities.hoverText(); //after type effect, replace appended letters with full dialog, run hoverText to split it into spans (for hover text)
            $("#responseButton").prop('disabled', false);
            $('#npc_dialog').html(game.data.NPCdialog);
        }
    }, 50);     
},

Upvotes: 1

Views: 826

Answers (2)

Amadan
Amadan

Reputation: 198314

You should be using setTimeout. You shouldn't be using strings in setTimeout.

Bad:

setTimeout('foo(32)', 1000);

Good:

setTimeout(function() {
  foo(32);
}, 1000);

Your problem from what I can see is that you initiate a new parallel setTimeout loop each time you hit your button, without ever cancelling the previous ones. This is the common pattern:

var fooTimer = 0;
$(element).click(function(evt) {
  // clear the previous timer, if any
  if (fooTimer) {
    clearInterval(fooTimer);
  }
  // start a new timer
  fooTimer = setInterval(function() {
    foo(32);
  }, 1000);
}

Another thing to note: setInterval/setTimeout are the only way to do this. You can't use a loop as a delay, because this way your function never relinquishes the thread. Since JavaScript is single-threaded, and shares that one thread with the UI repaint, your screen would never update. You'd wait for however much it takes to run all the loops for all the letters with the hourglass spinning and your browser being blocked from pretty much anything, then finally you'd see everything appear at once. That is, unless the browser doesn't think your code has gone awry and asks you if you'd like to terminate the bad script.

Upvotes: 2

Steve
Steve

Reputation: 7098

You can use setTimeout() or setInterval(); it would be the preferred (only?) way to do something like this.

I think the other SO user was referring to the practice of setting the callback code in a string rather than with a function reference as has been possible for some time now.

Upvotes: 0

Related Questions