Search code examples
jqueryjquery-animateincrementlettersalphabet

How can I create animated letter increments of a given word similar to the way animated number counters work?


I'm creating an "animated letter incrementer" that takes any given word and increments each letter of that word starting from A.

Example:

Word = Dog

D - Increments from A to D [A, B, C, D]
O - Increments from A to O [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O]
G - Increments from A to G [A, B, C, D, E, F, G]

The effect I'd like to achieve is similar to this jQuery animated number counter, except I'll be incrementing letters instead of counting numbers. Also, each letter of the word should increment independently, but they should all reach their destination letter at the same time.

JS:

var wordToIncrement = 'DOG';
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ '.split('');

for (var i = 0, len = wordToIncrement.length; i < len; i++) {

    var letterToIncrementTo = wordToIncrement[i];

    var arrayLength = alphabet.length;
    for (var z = 0; z < arrayLength; z++) {

        var alphabetLetter = alphabet[z];

        if (alphabetLetter == letterToIncrementTo) {

            console.log('MATCH FOUND!');
            console.log('Word Letter: ' + letterToIncrementTo);
            console.log('Alphabet Letter: ' + alphabetLetter);

            alphabetPositionValue = z;
            console.log('VALUE: ' + alphabetPositionValue);

            function incrementToLetter(letterToIncrementTo,alphabetPositionValue) {

                // This is where I'm stuck

                var div = document.getElementById('#word_block');
                div.innerHTML = div.innerHTML + 'Extra stuff';


            }

        }

    }

}

HTML:

<div id="work_block"></div>

How can I complete the code above to achieve similar functionality to the animated number counter example and increment through each letter of the word? I am looking for a javascript-based solution.


Solution

  • I would build a letter object maintaining the letter and timings. This way you can provide a simple update functionality on the object and the object will itself make sure it produces the correct current letter.

    For example:

    function Letter(table, letter, duration) {
      this.table = table;                          // lookup-table
      this.letter = letter;                        // target letter
      this.current = 0;                            // index in table
      this.delay = duration / tbl.indexOf(letter); // ms
      this.time = Date.now();                      // current (start) time
      this.done = false;                           // status
    }
    

    Then a common prototyped update() method:

    Letter.prototype.update = function() {
      if (this.done) return;                       // if done, do no more
      var time = Date.now();                       // get current time
      if (time - this.time >= this.delay) {        // exceeded delay?
        this.time = time;                          // store current time
        if (this.letter === this.table[this.current] || 
            this.current === this.table.length) {  // target reached? unknown letter
          this.done = true;                        // we're done
        }
        else {
          this.current++;                          // next in table
        }
      }
    };
    

    Then we can produce the objects from the string:

    var letters = [];
    word.toUpperCase().split("").forEach(function(l) {
      letters.push(new Letter(tbl, l, 2500));  // 2.5s duration
    });
    

    Then animate it:

    (function loop() {
       var txt = "", isDone = true;
       letters.forEach(function(l) {
         l.update();
         if (!l.done) isDone = false;
         txt += l.table[l.current];
       });
    
       // output txt
       if (!isDone) requestAnimationFrame(loop);
       else { /* done */ }
    })();
    

    Demo

    function Letter(table, letter, duration) {
      this.table = table;
      this.letter = letter;
      this.current = 0;
      this.delay = duration / tbl.indexOf(letter);   // ms
      this.time = Date.now();
      this.done = false;
    }
    Letter.prototype.update = function() {
      if (this.done) return;
      var time = Date.now();
      if (time - this.time >= this.delay) {
        this.time = time;
        if (this.letter === this.table[this.current] || 
            this.current === this.table.length) {
          this.done = true;
        }
        else {
          this.current++;
        }
      }
    };
    
    var word = "hello there";
    var tbl = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    var letters = [];
    word.toUpperCase().split("").forEach(function(l) {
      letters.push(new Letter(tbl, l, 2500))
    });
    
    (function loop() {
      var txt = "", isDone = true;
      letters.forEach(function(l) {
        l.update();
        if (!l.done) isDone = false;
        txt += l.table[l.current];
      });
    
      // output txt
      d.innerHTML = txt;
      
      if (!isDone) requestAnimationFrame(loop);
      else { /* done */ }
    })();
    #d {font:bold 32px monospace}
    <div id=d></div>

    Misc

    If the delay is below 16.7ms it may not increment fast enough. It can be solved by dividing current relative time on duration. Multiply this normalized value with index of the target letter in the table to get a current, just round the result to an integer value.

    You can provide different tables to obtain randomness/variation.