Arad
Arad

Reputation: 12833

Empty spaces are ignored by the InnerText property

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

Answers (7)

JJJ
JJJ

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

Zack Plauch&#233;
Zack Plauch&#233;

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

user6713871
user6713871

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

Monilito Castro
Monilito Castro

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

Niet the Dark Absol
Niet the Dark Absol

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

Gerardo Furtado
Gerardo Furtado

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

gurvinder372
gurvinder372

Reputation: 68433

You need to introduce the space using &nbsp; and use innerHTML instead of innerText

var paragOldText = parag.innerHTML;
parag.innerHTML = paragOldText + ( text[i].trim().length ? text[i] : "&nbsp;" ) ;

Edit

&nbsp; 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

Related Questions