Jamgreen
Jamgreen

Reputation: 11039

Animated typing in native JavaScript

I have tried creating an animated typing/erasing effect. It does type, but when it finishes the first sentence, it doesn't do anything else. It seems it is stuck in the very first if statement.

window.onload = () => {
  const sentences = ['Who am I?', 'Who are you?', 'Who are we?'];
  const input = document.getElementsByName('q')[0];

  let sentence = 0;
  let character = 0;
  let typing = true;

  (function typing() {

    if (character === sentences[sentence].length - 1) {
      typing = false;
    } else if (character === 0) {
      if (sentence < sentences.length - 1) {
        sentence++;
      } else {
        sentence = 0
      }
      typing = true;
    }

    if (typing) {
      character++;
    } else {
      character--;
    }

    input.placeholder = sentences[sentence].substring(0, character);

    setTimeout(typing, ~~(Math.random() * (300 - 60 + 1) + 60));
  })();

};

Upvotes: 1

Views: 153

Answers (2)

Jose Hermosilla Rodrigo
Jose Hermosilla Rodrigo

Reputation: 3683

Take a look at this approach using Promises and some Array functions:

var sentences = ['Who am I?', 'Who are you?', 'Who are we?'],
input = document.getElementsByName('q')[0];

// Waits for a time passed as parameter
let wait = ms =>new Promise( resolve => setTimeout(resolve, ms)),

isPaused,

// Function that returns a Promise that remove ONE character from the placeholder
removeOne = ()=> new Promise( (rs, rj) =>{
  setTimeout( ()=>{
    input.placeholder = input
      .placeholder
      .substring(0, input.placeholder.length - 1);
    rs();
  }, ~~( Math.random() * (300 - 60 + 1) + 60) );
}),

// Function that takes a char and return a Promise that add the char to placeholder
addOne = char => new Promise( (rs, rj) =>{
  if (isPaused) return rj('Paused');
  setTimeout( ()=>{
    input.placeholder += char;
    rs();
  }, ~~(Math.random() * (300 - 60 + 1) + 60));
}),

// Cleans the placeholder char by char sequentially
// as random as typed.
clean = ()=> Array.apply(null, { length : input.placeholder.length + 1 })
  .reduce( chain => chain.then(removeOne), Promise.resolve() ),
  
// Type one sentence into the placeholder sequentially
// then wait, then clean then wait
type = sent => sent.split('')
  .reduce( (chain, char)=> chain.then( addOne.bind(null, char) ), Promise.resolve() )
  .then(wait.bind(null, 1000))
  .then(clean)
  .then(wait.bind(null, 500)),
  //.catch(Promise.reject),

// Execute an infine loop that type each sentence
// sequentially
loop = ()=>{
  isPaused = false;
  return sentences.reduce( (chain, sent)=> chain
  .then( ()=> type(sent) )
  .catch( Promise.reject )
, Promise.resolve() )
.then(loop);
},

// Change the boolean to true, this will cause a rejection in addOne Promise
// then will call clean to clean the placeholder.
pause = ()=> {
  isPaused = true;
  return clean();
};

// Execute the loop at first time
loop();

// Pause the execution in 20 seconds
// As this is a promise you can also listen for pause to complete
// i.e. pause().then(wait.bind(null, 1000)).then(loop);
setTimeout(pause, 20000);
<input type = "text" name= "q">

Upvotes: 0

webdeb
webdeb

Reputation: 13211

You override your function in the function itself, setTimeout receives a boolean as first argument

For function names its better to use verbs like typeSentence
For boolean its good to use questions like isTyping

Upvotes: 1

Related Questions