4
\$\begingroup\$

I have implemented a Javascript TypeWriter effect for my website. It types out code using the TypeWriter variable by adding each character on and setting a timeout. It then 'deletes' code using text slice and 're types.

I had big problems in setting a timeout for the individual functions because each time I did the speed would double.

I just tried to get the code working before making it more practical. At the moment my code is very linear and not very dynamic. Can anyone suggest some edits to make to make it more fluid? I'm not advanced, please go easy on me!

 /* 
 typewriter.js
 Displays a typing effect on the home page.
 Tried to implement recursion instead of calling different functions but had trouble.
 **/
 var text1 = 'Hi, I\'m Ainsley I like';
 var text2 = ' love to code...';
 var id = 'typing-text1';
 var deleteUpTo = 17;
 var linebreak = 15;
 var speed = 150;
 var char = 0;
 var reType = 0;
 var textLength = text1.length;

 var typewrite = ' ';

 function typeWriter() {
    if (char < linebreak) {
       typewrite += text1[char];
       char++;
       setTimeout(typeWriter, speed);
       document.getElementById(id).innerHTML = typewrite;
    } else {
       typewrite += '<br />'
       printNewLine();
    }
 }

 function printNewLine() {
    if (char < text1.length) {
       typewrite += text1[char];
       char++;
       setTimeout(printNewLine, speed);
       document.getElementById(id).innerHTML = typewrite;
    } else {
       char = document.getElementById(id).innerHTML.length;
       deleteText();
    }
 }

 function deleteText() {
    if (char > 24) {
       typewrite = typewrite.slice(0, char);
       char--;
       document.getElementById(id).innerHTML = typewrite;
       setTimeout(deleteText, speed);
    } else {
       rePrintText();
    }
 }

 function rePrintText() {
    if (reType < text2.length) {
       typewrite += text2[reType];
       reType++;
       document.getElementById(id).innerHTML = typewrite;
       setTimeout(rePrintText, speed);
    }
 }
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

One loop to rule all animation.

For almost all realtime animations the whole thing is handled from one timed loop running at as close to a constant rate as is possible.

Having many different timers call many different functions quickly gets complicated and very hard to follow.

For the browser requestAnimationFrame should be used for any content animations. It calls a special functions that holds any changes made to the DOM in a back buffer until the next display vertical refresh (60 times a second)

This ensures that the animation is in sync with the display and any other DOM rendered content.

You use that function as the main loop to control all the associated animation content.

Simulating the typist

You have over complicated the typer. All that is needed is the current character position and a way to move and clear lines when a newline char is found.

The function called by requestAnimationFrame is given the current time since the page loaded. You can use that time to calculate the character position and then iterate over the characters needing to be added, checking for new lines.

A variable is kept currentLine that holds the current typing line and you just append text to that as you go.

To reduce the load you can reuse existing elements, removing it from the top, and appending it to the bottom. See newLine function in example below

Example

Rather than change your code, I have started from scratch. There is one main loop function called typer and a function to setup a new line. The character position is calculated by dividing the time since start by the speed.

If you want to speed up the rate of characters just subtract from the start time. I have added an example that randomly speeds up every 1/20 character. To slow down just add to the start time.

requestAnimationFrame(typer);

const words = `
 Testing type writer.
 And now some more text.
 You can remove some<<<<<<<<<<<delete text as well.
 Displays a typing effect on the home page.
 If you want to speed up the rate of characters just subtract from the start time.
 I have added an example that randomly speeds up every 1/20 character. 
 To slow down just add to the start time.`;

const leftChar = ":>";
const textContainer = document.getElementById('typingText');
const speed = 100;
var startTime;
var lastCharPos;
var currentLine;

for (let i = 0; i < 2; i++) {
  textContainer.appendChild(Object.assign(document.createElement("div"), {
    className: "typed--typed-text",
    textContent: ""
  }));
}
// removes top line, clears it and adds to bottom of container
function newLine() {
  currentLine = textContainer.querySelector(".typed--typed-text");
  textContainer.removeChild(currentLine);
  textContainer.appendChild(currentLine);
  currentLine.textContent = leftChar;
}


function typer(time) {
  var str = "";
  if (startTime === undefined) {
    newLine();
    startTime = time;
    lastCharPos = 0;
  }

  // 1 in 20 characters are 50ms faster
  if (Math.random() < 0.05) { startTime -= 50 }

  const charPos = (time - startTime) / speed | 0;
  while (lastCharPos <= charPos) {
    const char = words[(lastCharPos++) % words.length];
    if (char === "\n") {
      currentLine.textContent += str;
      str = "";
      newLine();
    } else if (char === "<") {
      const text = currentLine.textContent;
      currentLine.textContent = text.substring(0,text.length-1);
    } else {
      str += char;
    }
  }
  currentLine.textContent += str;
  requestAnimationFrame(typer);
}
.typed {}

.typed--typed-text {}
<div id="typingText" class="typed">
</div>

\$\endgroup\$
4
  • \$\begingroup\$ Wow thank you for your detailed answer. I had no idea that requestAnimationFrame existed. I will further look into it. With the code given. Would there be a way to implement a delete function where it deletes text and 're types? Also, how would I stop the loop after one iteration. As in, so it doesn't loop infinitely? Thanks again. \$\endgroup\$
    – Ains Clark
    Aug 19, 2018 at 12:46
  • \$\begingroup\$ @AinsClark to stop at the end. Change from the line that sets 'charPos' change const to a var as var charPos = (time - startTime) / speed | 0; then check its length charPos = Math.min(charPos, words.length - 1) to prevent typing past the end and to stop the animation dont call requestAnimationFrame when at end if(charPos < words.length -1) { requestAnimationFrame(typer) } \$\endgroup\$
    – Blindman67
    Aug 19, 2018 at 13:18
  • \$\begingroup\$ Thanks @Blindman67. Is there also an easy way to implement a delete function? Its the main problem I encountered when writing the original code. I've been trying and no luck (with the above code). \$\endgroup\$
    – Ains Clark
    Aug 21, 2018 at 13:54
  • \$\begingroup\$ @AinsClark Code review is not really a place to ask "How do I?" Go to stackoverflow for code problems. Anyways i have added backspace , it deletes characters for every < character it finds. See updated code snippet. \$\endgroup\$
    – Blindman67
    Aug 21, 2018 at 14:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.