Console-like printing of message JavaScript I'm learning JavaScript and I've made a very simple little document-modifying script that prints a message as if it was someone typing into a console. It practices recursion and in my opinion, I feel a bit iffy about the number of if-cases in the code. My main concern is the JavaScript though I know there's still a lot I have to learn about CSS animations.



var text = document.getElementById('text');
var greeting = ['Hello. I am a console wannabe.',
                'All systems are functioning.',
                'I like pie.'];
text.innerHTML = '&lt;i&gt;▮&lt;/i&gt;';

(function greet() {
    if (greeting.length &gt; 0 &amp;&amp; greeting.length &lt; 3) {
        text.insertBefore(document.createElement('br'), text.lastChild);
    }
    var line = greeting.shift();
    if (!line) {
        return;
    }
    line = line.split('');
    (function type() {
        var character = line.shift();
        if (!character) {
            return setTimeout(greet, 2000);
        }

        text.insertBefore(document.createTextNode(character), text.lastChild);
        setTimeout(type, 300);
    }());
}());
body{
    background-color: #000000;
    color: #99ffcc;
    font-family: Courier;
}

i{
    font-style: unset;
    font-size: 1em;
    animation: blink 1100ms linear infinite;
}

@keyframes blink {
    49% {
        opacity: 1;
    }
    50% {
        opacity: 0;
    }
    89% {
        opacity: 0;
    }
    90% {
        opacity: 1;
    }
}
&lt;div id="text"&gt;&lt;/div&gt;




I would like as many opinions as you can have about my simple little script. Does it look like I have a small grasp of JavaScript?
Your code is really easy to understand, and there's not much wrong with it.

I promise I'll go easy on you.




Right on the first lines, something bothers me...


You have this:

var text = document.getElementById('text');
var greeting = ['Hello. I am a console wannabe.',
                'All systems are functioning.',
                'I like pie.'];
text.innerHTML = '&lt;i&gt;▮&lt;/i&gt;';


What's wrong? The names!

text implies some actual text, but you have a &lt;div&gt;. greeting is alright, but it is actually text. And that &lt;i&gt; is a prompt.

I propose the following rewrite:

var screen = document.getElementById('screen');
var text = ['Hello. I am a console wannabe.',
                'All systems are functioning.',
                'I like pie.'];
screen.innerHTML = '&lt;b id="prompt" class="idle"&gt;&amp;marker;&lt;/b&gt;';


It all makes sense, except for the class, which I will explain later on.
And please, use the HTML entities, to avoid problems with file encodings.
Also, don't use &lt;i&gt; for this, since it will have problems when the animation is stopped.
The best would be to move the prompt to the HTML and ignore that line.


Lets analyse the function greet().


You have the following code:

(function greet() {
    if (greeting.length &gt; 0 &amp;&amp; greeting.length &lt; 3) {
        text.insertBefore(document.createElement('br'), text.lastChild);
    }
    var line = greeting.shift();
    if (!line) {
        return;
    }
    line = line.split('');
    (function type() {
        var character = line.shift();
        if (!character) {
            return setTimeout(greet, 2000);
        }

        text.insertBefore(document.createTextNode(character), text.lastChild);
        setTimeout(type, 300);
    }());
}());


Huh? The greet() function is preparing to type the text? And only works for this example? Well, let me re-write this for you.

function type(text, screen) {
    //You have to check for lines and if the screen is an element
    if(!text || !text.length || !(screen instanceof Element)) {
        return;
    }

    //if it is not a string, you will want to make it into one
    if('string' !== typeof text) {
        text = text.join('\n');
    }

    //normalize newlines, and split it to have a nice array
    text = text.replace(/\r\n?/g,'\n').split('');

    //the prompt is always the last child
    var prompt = screen.lastChild;
    prompt.className = 'typing';

    var typer = function(){
        var character = text.shift();
        screen.insertBefore(
            //newlines must be written as a `&lt;br&gt;`
            character === '\n'
                ? document.createElement('br')
                : document.createTextNode(character),
            prompt
        );

        //only run this again if there are letters
        if( text.length ) {
            setTimeout(typer, 300);
        } else {
            prompt.className = 'idle';
        }
    };
    setTimeout(typer, 300);
};


Wow, some fine German Overengeneering going on there!

You can pass it a string or an array of strings, and a 'screen', where the message will be added.


Now, enough of Javascript, lets talk about CSS!


It is a pretty straightforward CSS, without many complications.
But, remember that it suffered a rewrite.

Replace your i CSS with this:

#prompt{
    font-style: unset;
    font-size: 1em;
}
#prompt.idle {
    animation: blink 1100ms linear infinite;
    -webkit-animation: blink 1100ms linear infinite;
}



On your HTML, simply change the id.


Remember, we re-wrote it:

&lt;div id="screen"&gt;&lt;b id="prompt" class="idle"&gt;&amp;marker;&lt;/b&gt;&lt;/div&gt;




Final result:

And now, all together:



function type(text, screen) {
 //You have to check for lines and if the screen is an element
 if(!text || !text.length || !(screen instanceof Element)) {
  return;
 }
 
 //if it is not a string, you will want to make it into one
 if('string' !== typeof text) {
  text = text.join('\n');
 }
 
 //normalize newlines, and split it to have a nice array
 text = text.replace(/\r\n?/g,'\n').split('');
 
 //the prompt is always the last child
 var prompt = screen.lastChild;
 prompt.className = 'typing';
 
 var typer = function(){
  var character = text.shift();
  screen.insertBefore(
   //newlines must be written as a `&lt;br&gt;`
   character === '\n'
    ? document.createElement('br')
    : document.createTextNode(character),
   prompt
  );
  
  //only run this again if there are letters
  if( text.length ) {
   setTimeout(typer, 300);
  } else {
   prompt.className = 'idle';
  }
 };
 setTimeout(typer, 300);
};

window.onload=function(){
 var screen = document.getElementById('screen');
 var text = [
  'Hello. I am a better console wannabe.',
  'All systems are functioning.',
  'I like pie.'
 ];
  
 type(text, screen);
};
body {
 background-color: #000000;
 color: #99ffcc;
 font-family: Courier;
}
#prompt {
 font-style: unset;
 font-size: 1em;
}
#prompt.idle {
 animation: blink 1100ms linear infinite;
 -webkit-animation: blink 1100ms linear infinite;
}

@keyframes blink {
 49% {opacity: 1;}
 50% {opacity: 0;}
 89% {opacity: 0;}
 90% {opacity: 1;}
}
@-webkit-keyframes blink {
 49% {opacity: 1;}
 50% {opacity: 0;}
 89% {opacity: 0;}
 90% {opacity: 1;}
}
&lt;div id="screen"&gt;&lt;b id="prompt" class="idle"&gt;&amp;marker;&lt;/b&gt;&lt;/div&gt;






To ressemble a little more a console, the animation is only applyed when there's no typing.

With minor changes, it is possible to make it work with keypresses on the keyboard. And with a write queue. But that's left as an exercise for the O.P.



Also, one cool thing you can try is to use a random timeout, to create a varying effect on the writting, to simulate real typing! You can even add an extra interval to special characters, to be more human-like:



function type(text, screen) {
 //You have to check for lines and if the screen is an element
 if(!text || !text.length || !(screen instanceof Element)) {
  return;
 }
 
 //if it is not a string, you will want to make it into one
 if('string' !== typeof text) {
  text = text.join('\n');
 }
 
 //normalize newlines, and split it to have a nice array
 text = text.replace(/\r\n?/g,'\n').split('');
 
 //the prompt is always the last child
 var prompt = screen.lastChild;
 prompt.className = 'typing';
 
 var typer = function(){
  var character = text.shift();
  screen.insertBefore(
   //newlines must be written as a `&lt;br&gt;`
   character === '\n'
    ? document.createElement('br')
    : document.createTextNode(character),
   prompt
  );
  
  //only run this again if there are letters
  if( text.length ) {
   var delay, next = text[0];
   //based on a querty pt-PT keyboard, there delays are subjective
   if(next.match(/[a-z\d\t\-\.,º]/)){
    //fastest characters
    delay = 50;
   } else if(next == ' ' || next == '\n' || next.match(/[\\\|\!\"\#\$\%\&amp;\/\(\)\=\?\'»\*ª_:;&gt;A-Z]/)) {
    //medium-slow keys
    delay = 100;
   } else if(next.match(/[\@\€\£\§\{\[\]\}\~\´]/)) {
    //slow keys
    delay = 150;
   } else {
    //Yes, that slow!
    delay = 250;
   }
   //repeated characters are types faster
   if(next == character) {
    delay -= 25; //reduces the delay by 50
   }
   
   setTimeout(typer, delay + (Math.random() * 50));
  } else {
   prompt.className = 'idle';
  }
 };
 setTimeout(typer, 50 + (Math.random() * 50));
};

window.onload=function(){
 var screen = document.getElementById('screen');
 var text = [
  'Hello. I am a better console wannabe.',
  'All systems are functioning.',
  'I like pie.',
  'É só pra teste (just for testing).',
  'ASCII PARTY!"#$%&amp;/()=?!!!!!'
 ];
  
 type(text, screen);
};
body {
 background-color: #000000;
 color: #99ffcc;
 font-family: Courier;
}
#prompt {
 font-style: unset;
 font-size: 1em;
}
#prompt.idle {
 animation: blink 1100ms linear infinite;
 -webkit-animation: blink 1100ms linear infinite;
}

@keyframes blink {
 49% {opacity: 1;}
 50% {opacity: 0;}
 89% {opacity: 0;}
 90% {opacity: 1;}
}
@-webkit-keyframes blink {
 49% {opacity: 1;}
 50% {opacity: 0;}
 89% {opacity: 0;}
 90% {opacity: 1;}
}
&lt;div id="screen"&gt;&lt;b id="prompt" class="idle"&gt;&amp;marker;&lt;/b&gt;&lt;/div&gt;