2

I'm trying to create a basic typewriter with pure js.

I'm basically trying to emulate somebody typing with a keyboard in a believable fashion. Issue is, for some reason, my node's text doesn't seem to display and I can't seem to figure out why

function get_random_in_interval(min, max){
   return Math.floor(Math.random() * (max - min + 1)) + min;
 }

function typewriter(textinput){
  var min = 1;
  var max = 3;
  var rand = get_random_in_interval(min, max);
  var into = document.getElementById('textentry');


  for (i = 0; i < textinput.length; i++){
        var timer = setInterval(function(){
        if (i >= textinput.length){
          clearInterval(timer);
        }

        into.innerHTML += textinput.charAt(i);
      }, rand * 1000);
  }

}


 var test = "Hello, I am a <b>text</b> \n \n I tried doing some freaky stuff";
 typewriter(test);
#textentry {
  border: 10px solid #2c3e50;
  width: 400px;
  height: 100px;
  background-color: #95a5a6;
  font-family: Consolas, monospace;
  color: #FFF;
}
<div id="textentry"></div>

My best guess would be that I'm using setInterval() function incorrectly here. Issue is, I can't really think of where else should I place it. If i placed the setInterval() outside of for() loop, then it would work (tested it myself), but It would print the entire string at random intervals and not only the desired char.

Samuel Hulla
  • 6,617
  • 7
  • 36
  • 70
  • 1
    Do you really want to set a new interval with every single loop iteration? Also, `i` will always equal `textinput.length` inside your interval function. See [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/q/750486/4642212). – Sebastian Simon Jul 01 '18 at 23:05

3 Answers3

2

Here is a more simpler solution (compared to the other one):

This basically uses a setInterval and then stops after i becomes more than length.

function get_random_in_interval(min, max){
   return Math.floor(Math.random() * (max - min + 1)) + min;
 }

function typewriter(textinput){
  var min = 1;
  var max = 3;
  var rand = get_random_in_interval(min, max);
  var into = document.getElementById('textentry');
  var i = 0;
  let Htmlstring = ""; // Credit to Certain Performance for the idea

  var timer = setInterval(function(){
    Htmlstring += textinput.charAt(i);
    into.innerHTML = Htmlstring;
    i++;
    if (i > textinput.length) {
      clearInterval(timer);
    }
  }, get_random_in_interval(min, max)*30);
  

}


 var test = "Hello, I am a <b>text</b> \n \nI tried doing some freaky stuff";
 typewriter(test);
#textentry {
  border: 10px solid #2c3e50;
  width: 400px;
  height: 100px;
  background-color: #95a5a6;
  font-family: Consolas, monospace;
  color: #FFF;
}
<pre id="textentry"></pre>
Sheshank S.
  • 3,053
  • 3
  • 19
  • 39
2

There are some good answers here. I'm posting this just as an different alternative since there are a lot of ways to do this. You can use setTimeout instead of setInterval with a small recursive function.

function get_random_in_interval(min, max){
    return Math.floor(Math.random() * (max - min + 1)) + min;
    }

function typewriter(textinput){
    const min = 60;
    const max = 120;
    const into = document.getElementById('textentry');
    const type = (i) => {
        let rand = get_random_in_interval(min, max);
        setTimeout(() => {
            into.innerHTML = textinput.slice(0,i++)
            if (i < textinput.length) type(i)
        }, rand)
    }
    type(0)
}

var test = "Hello, I am a <b>text</b> \n \n I tried doing some freaky stuff";
typewriter(test);
#textentry {
  border: 10px solid #2c3e50;
  width: 400px;
  height: 100px;
  background-color: #95a5a6;
  font-family: Consolas, monospace;
  color: #FFF;
}
<div id="textentry"></div>
Mark
  • 90,562
  • 7
  • 108
  • 148
1

All the intervals are being set synchronously. By the time the typewriter function ends (before any intervals have run), i is equal to textinput.length, so into.innerHTML += textinput.charAt(i); doesn't work.

You also want to pause before each iteration - to do this, it would probably be easiest to await a Promise in the loop. (You definitely don't want setInterval)

const get_random_in_interval = (min, max) => (
  new Promise(res => setTimeout(res, Math.floor(Math.random() * (max - min + 1)) + min))
);

async function typewriter(textinput){
  var min = 5;
  var max = 45;
  var into = document.getElementById('textentry');
  let htmlStr = '';
  for (let i = 0; i < textinput.length; i++){
    await get_random_in_interval(min, max);
    htmlStr += textinput.charAt(i);
    into.innerHTML = htmlStr;
  }
}
 var test = "Hello, I am a <b>text</b> <br> <br> I tried doing some freaky stuff";
 typewriter(test);
#textentry {
  border: 10px solid #2c3e50;
  width: 400px;
  height: 100px;
  background-color: #95a5a6;
  font-family: Consolas, monospace;
  color: #FFF;
}
<div id="textentry"></div>

Replace the \ns with <br>s if you want to type HTML.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • **Promises FTW!** ...although I'm curious, how would you solve this via the callback way (`typewriter('text', cb)`), without Promises. – user7637745 Jul 01 '18 at 23:10
  • Holy crap, that's awesome! First time I've seen `Promise` utilized like this. I'll try to study up on it tomorrow, to see if I can comprehend it better, optionally see if I have any questions to your implementation. As of now, it seems I've got some reading to do, – Samuel Hulla Jul 01 '18 at 23:15
  • 1
    @RichardSzakacs Either with recursion (have the initial `typewriter` call some other function which has a `setTimeout` and recursively calls itself) or by calculating all increments ahead of time and calling every `setTimeout` at once – CertainPerformance Jul 01 '18 at 23:16
  • Should be an easy fix though. – Sheshank S. Jul 01 '18 at 23:16
  • @CertainPerformance awesome, you just gave me my morning coding exercise for the coffee. **Thanks!** – user7637745 Jul 01 '18 at 23:19