5

I want to simulate an autocomplete function. A sentence should be completed with words from a list. These words should be displayed char by char at the end of the sentence.

This is the HTML:

<p>We can help you with<span id="complete"></span></p>

This is part of the JS:

let words = ["design", "frontend", "backend"];
let output = document.getElementById("complete");

First I tried this:

words.forEach((e) => {
  for (i = 0; i < e.length; i++) {
    setTimeout(() => {
      console.log(e[i]);
    }, 500);
  }
});

console logged:

enter image description here

I think it is because the iterator steps forward before the setTimeout.

So I tried a while loop like this:

words.forEach((e) => {
  let i = 0;
  while (i <= e.length) {
    setTimeout(() => {
      console.log(e[i]);
    }, 600);
    i++;
  }
});

I got this:

enter image description here

I have no idea how to place character by character – so that they add up to the word like: d; de; des; desi; desig; design with kind of a delay between each character.

(To place the words char by char in the DOM, I would not use console log, but complete.innerHTML += variableForWordinList[index for char])

Thank you

AbsoluteBeginner
  • 2,160
  • 3
  • 11
  • 21
S.H
  • 671
  • 2
  • 6
  • 22

4 Answers4

2

setTimeout is asynchronous. So by the time it executes the function, the value you are logging has already changed.
What are you trying to achieve is a sort of typewriter Effect. It can be achieved using a combo of setInterval and setTimeout.

let words = ["design", "frontend", "backend"];
let output = document.getElementById("complete");

let idx = 0;
let str = "";

function typeWrite() {
  if (idx >= words.length) idx = 0;

  let word = words[idx];
  let charInd = 0;
  let interval = setInterval(() => {
    if (charInd >= word.length) {
      clearInterval(interval);
      setTimeout(() => {
        idx++;
        str = "";
        typeWrite();
      }, 1000);  // <- Wait for 1 sec before printing next word
    }
    str += word.charAt(charInd);
    output.innerHTML = str;
    charInd++;
  }, 100); // <- Delay between each character
}

typeWrite();
<p>We can help you with <span id="complete"></span></p>
TechySharnav
  • 4,869
  • 2
  • 11
  • 29
1

I think it is because the iterator steps forward before the setTimeout.

If you add a console.log(i) to the loop, you'll see unexpected value, so your assumptions seems about right.


Since probably want to show the 3 words after each other, not combined, we'll need another way to 'wait' after the first word has been 'typed'

There are many libraries out there, like TypewriterJs or one of jQuery's plugins, but for sake of learning, let's try to make one ourself.

let words = ["design", "frontend", "backend"];
let output = document.getElementById("complete");

const s = ms => new Promise(resolve => setTimeout(resolve, ms));

async function showWord(w) {
  output.innerHTML = '';
  for (let i = 0; i < w.length; i++) {
    await s(150);
    output.innerHTML += w[i];
  }
  await s(500);
}

words.reduce((c, w) =>
  c.then(showWord.bind(null, w))
, showWord(words.shift()));
<p>We can help you with <span id="complete"></span></p>

The s function uses Promises to 'wait' for some milliseconds.

Then we define a async function that will 'type' our word, and delay when needed.

Using reduce we can loop through the array of words, using shift to remove it from the array

0stone0
  • 34,288
  • 4
  • 39
  • 64
0

I use it for the case substring. Like that:

words = ['Hello World'];

function showC(words) {
  let str = words.join()
  for(let i = 0; i <= str.length; i++) {      
            setTimeout(() => {             
             console.log(str.substring(0, i));
         
        }, 500*i);    
  }
}
showC(words)
Maik Lowrey
  • 15,957
  • 6
  • 40
  • 79
0

I think this is what you're expecting? :

let words = ["design", "frontend", "backend"];
let output = document.getElementById("complete");

words.forEach((e, index) => {
  for (let i = 0; i < e.length; i++) {
    setTimeout(() => {
      console.log(e[i]);
    }, 500 * i * index);
  }
});
Kanishk Anand
  • 1,686
  • 1
  • 7
  • 16