Reputation: 12833
I'm trying to write a function (in JavaScript) that would write a sentence in a <p>
tag by writing its letters one by one with a 300ms pause between each letter, for exmaple. I've written the following:
var text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", "y", "o", "u", "?"]
function typeText() {
var i = 0;
var interval = setInterval(function () {
var parag = document.getElementById("theParagraph");
var paragOldText = parag.innerText;
parag.innerText = paragOldText + text[i];
i++;
if (text.length == i)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
As you can see, there are some " " (empty space) characters in the array; the problem is that it doesn't write those empty spaces, so the sentence would be like this: "Hellohowareyou". How do I solve this?
Upvotes: 33
Views: 18118
Reputation: 33153
Don't use presentation as data. Store the current content as a separate string, don't pull it from the DOM. This way you're not dependent on how the browser stores the element's text content.
const text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", "y", "o", "u", "?"]
const parag = document.getElementById("theParagraph");
function typeText() {
var i = 0;
var paragText = "";
var interval = setInterval(function() {
paragText += text[i];
parag.innerText = paragText;
i++;
if (text.length == i)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
As a side note, the same thing could be made a lot simpler:
const text = "Hello how are you?";
const parag = document.getElementById("theParagraph");
function typeText() {
var i = 0;
var interval = setInterval(function () {
parag.innerText = text.substr(0, i);
if (text.length == i)
clearInterval(interval);
i++;
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
Upvotes: 36
Reputation: 4240
Short answer: Use textContent
attribute instead of innerText
attribute and you'll be able to add spaces.
e.g.
var text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", " ", "y", "o", "u", "?"] // Added missing space after "are"
function typeText() {
var i = 0;
var interval = setInterval(function() {
var parag = document.getElementById("theParagraph");
var paragOldText = parag.textContent; // Replaced "parag.innerText" with "parag.textContent"
parag.textContent = paragOldText + text[i]; // Did it again.
i++;
if (text.length == i)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
Also, please note that Konrad Rudolph and bergi answered the why in comments directly on the question.
Upvotes: 1
Reputation:
I've modified your code to show how you can use the slice method for shorter, more elegant code.
var text = "Hello how are you?"
function typeText() {
var i = 0;
var parag = document.getElementById("theParagraph");
var interval = setInterval(function () {
i++;
parag.innerText = text.slice(0, i);
if (i == text.length)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
Upvotes: 1
Reputation: 12
This problem is a great candidate for an MVC pattern. I discuss this exact problem in my blog. I've provided an MVC for this problem below. (Please excuse the shameless self-promotion.)
const Model = function(){
const self = this;
self.index = 0;
self.text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", " ", "y", "o", "u", "?"];
self.textString = "",
self.accumulate = function(){
const length = self.text.length;
self.textString = self.textString + self.text[self.index];
self.index = ++self.index % length;
}
}
const Controller = function(model, elem, milsec){
const self = this;
self.elem = elem;
self.start = function(){
const interval = setInterval( function(){
if(model.index===model.text.length-1){
clearInterval(interval);
}
model.accumulate();
self.elem.innerText = model.textString;
}, milsec);
}
}
const typeText = function(){
const model = new Model();
const theParagraph = document.getElementById('theParagraph');
const controller = new Controller(model, theParagraph, 200);
controller.start();
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
<p>
I invite you to go to my <a target='_top' href="https://www.monilito.com/blog/Never-Use-Presentational-Structures-to-Store-State">blog article</a> for an interesting take on this problem.
</p>
</body>
Upvotes: -3
Reputation: 324770
The other answers address the issues with your code, but I'd like to address issues with your whole plan.
Do you really want to be defining an array of characters? Long sentences are going to be hell. And what if you want variable text? Use this instead:
var input = "Hello how are you?";
var text = input.split(""); // split into array of characters
Speaking of longer sentences, your "typewriter" will fill out the current line, realise it doesn't have room, and then bump the last word down to the next line to finish it. This is not a good look! You can get around this with a clever trick:
<p><span id="visible_text">Hello how a</span><span id="remaining_text">re you?</span></p>
<style>#remaining_text {visibility:hidden}</style>
Not only will this handle word wrapping very nicely, it will also "reserve" the necessary space ahead of time so that you don't end up with it pushing the content below the typewriter further down the page as new lines arise.
You can easily achieve this effect by counting which character position you are at, then splitting the input
string into two pieces at that offset. Put the first piece in the first <span>
, the rest in the second, and you're golden.
Source: I use this technique in my "RPG cutscene"-style code. Actually a more advanced version, as mine also supports HTML rather than just plain text!
Upvotes: 4
Reputation: 102198
What about using textContent
?
var text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", " ","y", "o", "u", "?"]
function typeText() {
var i = 0;
var interval = setInterval(function() {
var parag = document.getElementById("theParagraph");
var paragOldText = parag.textContent;
parag.textContent = paragOldText + text[i];
i++;
if (text.length == i)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
You can also use innerHTML
:
var text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", " ", "y", "o", "u", "?"]
function typeText() {
var i = 0;
var interval = setInterval(function() {
var parag = document.getElementById("theParagraph");
var paragOldText = parag.innerHTML;
parag.innerHTML = paragOldText + text[i];
i++;
if (text.length == i)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
innerText
was introduced by IE and, as we all know, nothing good comes from IE. Joking apart, this is a good explanation about it: "The poor, misunderstood innerText".
Upvotes: 20
Reputation: 68433
You need to introduce the space using
and use innerHTML
instead of innerText
var paragOldText = parag.innerHTML;
parag.innerHTML = paragOldText + ( text[i].trim().length ? text[i] : " " ) ;
Edit
isn't required with innerHTML
var paragOldText = parag.innerHTML;
parag.innerHTML = paragOldText + text[i] ;
Demo
var text = ["H", "e", "l", "l", "o", " ", "h", "o", "w", " ", "a", "r", "e", "y", "o", "u", "?"]
function typeText() {
var i = 0;
var interval = setInterval(function() {
var parag = document.getElementById("theParagraph");
var paragOldText = parag.innerHTML;
parag.innerHTML = paragOldText + text[i];
i++;
if (text.length == i)
clearInterval(interval);
}, 200)
}
<body>
<p id="theParagraph"></p>
<button id="typeButton" onclick="typeText()" style="padding:15px">Start typing the sentence</button>
</body>
Upvotes: 1