skip to Main Content

I am attempting to create code that prints a string in a typewriter fashion. After that, it should erase the last word and type the first word from an array. Then, it should erase it after a couple of seconds and type the second word in the same array, and so on. After the last word has been typed, it should go back to the beginning and start typing the first word again, all while maintaining the typewriter effect.

This script should run when an HTML element with an id of ‘btn’ is clicked. Here is what I have been able to come up with:

var i = 0;
var j = 0;
var txt = "To me, you are extremely cool";
var array = [" pretty", " smart", " beautiful", " gorgeous", " cool"];
var speed = 50;

document.getElementById("btn").onclick = function () {
  typeWriter();
  setInterval(rotate, 5000);
};

function typeWriter() {
  if (i < txt.length) {
    document.getElementById("demo").innerHTML += txt.charAt(i);
    i++;
    setTimeout(typeWriter, speed);
  }
}

function rotate() {
  let demo = document.getElementById("demo");
  let words = demo.innerHTML.split(" ");
  words.pop();
  demo.innerHTML = words.join(" ");
  document.getElementById("demo").innerHTML += array[j];
  j = (j + 1) % array.length;
}
<button id="btn" type="button">Type</button>
<div id="demo"></div>

The issue is that I cannot successfully combine the previous code with the last word being erased and another one being typed with the typewriter effect. This merely changes the last word. What you see is the result of hours of futile attempts.

I would really appreciate your input, whether directly or indirectly useful towards my issue. Do you think this issue could be resolved with CSS?

EDIT: Here is the solution provided by async await and slightly modified by me to fit my needs:

document.getElementById("btn").onclick = function () {
  main();
};

function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function typewrite(txt, el) {
  el.innerText = "";
  for (const char of txt) {
    el.textContent += char;
    await sleep(100);
  }
}

async function typewrite2(txt, el) {
  for (const char of txt) {
    el.textContent = el.textContent.slice(0, -1);
    await sleep(250);
  }
}

async function main() {
  const TAILS = ["pretty", "smart", "beautiful", "gorgeous", "cool"];
  const ROOT_TEXT = "I think you are ";
  const root = document.querySelector(".root");
  const tail = document.querySelector(".tail");
  await typewrite(ROOT_TEXT, root);
  let tailIndex = 0;
  while (true) {
    await typewrite(TAILS[tailIndex], tail);
    await sleep(1000);
    await typewrite2(TAILS[tailIndex], tail);
    await sleep(1000);
    tailIndex = (tailIndex + 1) % TAILS.length;
  }
}
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  
.display {
    box-shadow: 0 0 2px 2px pink;
    padding: 1rem;
    border-radius: 5px;
  }

#btn {
    padding: 30px;
    border-radius: 6px;
    left: 50px;
    position: absolute;
    cursor: pointer;
}
<body>
    <button id="btn" type="button">Type</button>
    <div class="display">
        <span class="root"></span>
        <span class="tail"></span>
    </div>
</body>

2

Answers


  1. Does this code solves your problem? if not explain your desired result more so i can help further

    var i = 0;
    var j = 0;
    var x = 0;
    var txt = "To me, you are extremely ";
    var array = ["cool", "pretty", "smart", "beautiful", "gorgeous"];
    var speed = 50;
    const demo = document.querySelector('#demo');
    
    document.getElementById("btn").onclick = function () {
      if(demo.innerHTML){
        demo.innerHTML = '';
      }
      typeWriter();
    };
    
    function typeWriter() {
      if (i < txt.length) {
        demo.innerHTML += txt.charAt(i);
        i++;
        setTimeout(typeWriter, speed);
      } else {
        if(j != array.length){
            if(x >= array[j].length){
                demo.innerHTML = demo.innerHTML.replace(array[j], '');
                j++
                x = 0;
                setTimeout(typeWriter, speed);
            } else {
                demo.innerHTML += array[j].charAt(x);
                x++
                setTimeout(typeWriter, speed);
            }
        } else {
            j = 0;
            i = 0;
        }
      }
    }
    <button id="btn">click</button>
    <div id="demo"></div>

    Here is another similar solution i have created it last few weeks using requsetAnimationFrame API, it might help you

    function animateText(el, txtArr, delay) {
        let i = 0,
            currentTxt = 0,
            reverse = false;
        const oneFrame = () => {
            requestAnimationFrame(() => {
                if(!reverse){
                    el.textContent += txtArr[currentTxt][i];
                    i++;
                    if(i == txtArr[currentTxt].length){
                        reverse = true
                    }
                } else {
                    el.textContent = txtArr[currentTxt].substring(0, i);
                    i--;
                    if(i < 0){
                        i = 0;
                        reverse = false
                        currentTxt = (currentTxt < txtArr.length -1) ? currentTxt+=1 : 0;
                    }
                }
            });
            setTimeout(oneFrame, delay);
        };
        setTimeout(oneFrame, delay);
    }
    
    document.querySelector('#btn').addEventListener('click', ()=> {
      animateText(document.querySelector('#demo'), ["cool", "pretty", "smart", "beautiful", "gorgeous"], 100);
    });
    <button id="btn">click</button>
    <div><span>To me, you are extremely </span><span id="demo"></span></div>
    Login or Signup to reply.
  2. Tying to stay true to your example, there are a few things I changed. Instead of removing the last word, I created a variable ROOT_TEXT which I will use to reset the value of innerHTML. You already have a function for typewriting, so all I had to do was update the new text to typewrite, and the new spot to begin typewriting at every time there is a rotation. Hopefully this makes sense.

    var i = 0;
    var j = 0;
    const ROOT_TEXT = "To me, you are extremely";
    var txt = "To me, you are extremely cool";
    var array = [" pretty", " smart", " beautiful", " gorgeous", " cool"];
    var speed = 50;
    
    document.getElementById("btn").onclick = function () {
      typeWriter();
      setInterval(rotate, 5000);
    };
    
    function typeWriter() {
      if (i < txt.length) {
        document.getElementById("demo").innerHTML += txt.charAt(i);
        i++;
        setTimeout(typeWriter, speed);
      }
    }
    
    function rotate() {
      let demo = document.getElementById("demo");
      demo.innerHTML = ROOT_TEXT;
      i = ROOT_TEXT.length;
      txt = ROOT_TEXT + array[j];
      typeWriter();
      j = (j + 1) % array.length;
    }
    <div id="demo"></div>
    <button id="btn">clicky</button>

    I also wanted to add an example of how I might accomplish this. Coming from python you’re probably familiar with time.sleep and async code. Javascript uses promises to emulate async behavior. There will be a function called sleep which returns a promise resolving after a timeout, and inside async functions I can use the await keyword to stall code until the promise resolves.

    function sleep(ms) {
      return new Promise(r => setTimeout(r, ms));
    }
    
    async function typewrite(txt, el) {
      el.innerText = "";
      for (const char of txt) {
        el.textContent += char;
        await sleep(75);
      }
    }
    
    async function main() {
      const TAILS = ["pretty", "smart", "beautiful", "gorgeous", "cool"];
      const ROOT_TEXT = "I think you are ";
      const root = document.querySelector(".root");
      const tail = document.querySelector(".tail");
      await typewrite(ROOT_TEXT, root);
      let tailIndex = 0;
      while (true) {
        await typewrite(TAILS[tailIndex], tail);
        await sleep(3000);
        tailIndex = (tailIndex + 1) % TAILS.length;
      }
    }
    
    main();
    body {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .display {
      box-shadow: 0 0 2px 2px pink;
      padding: 1rem;
      border-radius: 5px;
    }
    <div class="display">
      <span class="root"></span>
      <span class="tail"></span>
    </div>

    I would recommend looking into why using let and const are better than var in most cases. One major thing that is different about the way I wrote my example, is that it is more reusable, packaged into functions that contain their scope. And I don’t use i or j to represent indexes, instead when I use an index, I make it clear what the index is relating to. for... of loops are huge for readability as well. If I wanted to improve upon this I might make a blinking cursor at the end of typing with css. I might change the regular intervals into humanlike spacing between times typed. I might make it so that the ending text is deleted in typewriter fashion instead of all at once. I might make the last word glow, or rotate colors with css.

    Best of luck on your journey and hopefully my example solved your problem and helped you understand some other potential of js! I came from python too, and I love js. The ability to put code right into a browser or website feels like magic to me 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search