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 = '<i>▮</i>';
What's wrong? The names!
text
implies some actual text, but you have a <div>
. greeting
is alright, but it is actually text
. And that <i>
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 = '<b id="prompt" class="idle">▮</b>';
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 <i>
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 > 0 && greeting.length < 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 `<br>`
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:
<div id="screen"><b id="prompt" class="idle">▮</b></div>
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 `<br>`
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;}
}
<div id="screen"><b id="prompt" class="idle">▮</b></div>
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 `<br>`
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(/[\\\|\!\"\#\$\%\&\/\(\)\=\?\'»\*ª_:;>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!"#$%&/()=?!!!!!'
];
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;}
}
<div id="screen"><b id="prompt" class="idle">▮</b></div>