Home > database >  How do I get my type effect function to run consecutively on an array of elements in using foreach m
How do I get my type effect function to run consecutively on an array of elements in using foreach m

Time:07-10

I have a written a function that types out each character of a given string. However, when I used a forEach method to loop over an array of headings they all type at the same time. Does anyone know how I can make these run one after the other?

HTML

    <section id="skills">
      <div >
        <h2></h2>
        <h2></h2>
        <h2></h2>
        <h2></h2>
        <h2></h2>
        <h2></h2>
        <h2></h2>
        <h2></h2>
        <h2></h2>
      </div>
    </section>

JAVASCRIPT

Create an array of h2s

const skillsHeaders = document.querySelectorAll(".skills-content h2");

Array of heading content to be applied to the headings

const skillsHeaderContent = [
  "HTML",
  "CSS",
  "JAVASCRIPT",
  "SASS",
  "GIT",
  "GITHUB",
  "REACT",
];

Given the parameters of an element and a string. Types out the given string in the given element.

function typeEffect(element, str) {
  let typedLetters = "";
  let i = 0;
  const interval = setInterval(() => {
    

typedLetters = str.slice(0, i);
    element.textContent = typedLetters;

if (i === element.length) {
  clearInterval(interval);
}
i  ;
  }, 120);
}

Loop over every element in the array of h2s and run the type effect function to create text content.

skillsHeaders.forEach((element, index) => {
  typeEffect(element, skillsHeaderContent[index]);
});

CodePudding user response:

The reason that you see the "typing" effect on all headers at the same time is that setTimeout doesn't block the thread, which means it runs in the background, allowing your forEach loop proceeds to the next element, set a new timer for it, and vice versa.

You can use a setTimeout to delay the call of the typeEffect function. Also, in your code, you referred to element.length, which is undefined, resulting that intervals will not be cleared. I changed it to typedLetters == str, comparing the text currently typed to the expected result.

const skillsHeaders = document.querySelectorAll(".skills-content h2");
const skillsHeaderContent = [
  "HTML",
  "CSS",
  "JAVASCRIPT",
  "SASS",
  "GIT",
  "GITHUB",
  "REACT",
];

const DELAY_PER_LETTER = 120;

function typeEffect(element, str) {
  let typedLetters = "";
  let i = 0;
  const interval = setInterval(() => {
    typedLetters = str.slice(0, i);
    element.textContent = typedLetters;

    if (typedLetters === str) {
      clearInterval(interval);
    }
    i  ;
  }, DELAY_PER_LETTER);
}

let delay = 0;
skillsHeaders.forEach((element, index) => {
  if (index >= skillsHeaderContent.length) return;
  const typingDuration = skillsHeaderContent[index].length * DELAY_PER_LETTER;
  setTimeout(() => {
    typeEffect(element, skillsHeaderContent[index]);
  }, delay);
  delay  = typingDuration;
});
<section id="skills">
  <div >
    <h2></h2>
    <h2></h2>
    <h2></h2>
    <h2></h2>
    <h2></h2>
    <h2></h2>
    <h2></h2>
    <h2></h2>
    <h2></h2>
  </div>
</section>

Another thing to note is you are using an array to define the content per each header, which in your example has fewer items than the headers themselves, which requires you to check for the index to be in the range of the array.

You can add a data attribute to each heading, then use javascript to extract the value from your HTML markup, and apply the functions only to headings that have text to be typed.

const skillsHeaders = document.querySelectorAll(".skills-content h2[data-typeeffect]");

const DELAY_PER_LETTER = 120;

function typeEffect(element, str) {
  let typedLetters = "";
  let i = 0;
  const interval = setInterval(() => {
    typedLetters = str.slice(0, i);
    element.textContent = typedLetters;

    if (typedLetters == str) {
      clearInterval(interval);
    }
    i  ;
  }, DELAY_PER_LETTER);
}

let delay = 0;
skillsHeaders.forEach((element, index) => {
  const text = element.getAttribute('data-typeeffect');
  const typingDuration = text.length * DELAY_PER_LETTER;
  setTimeout(() => {
    typeEffect(element, text);
  }, delay);
  delay  = typingDuration;
});
<section id="skills">
  <div >
    <h2 data-typeeffect="HTML"></h2>
    <h2 data-typeeffect="CSS"></h2>
    <h2 data-typeeffect="JAVASCRIPT"></h2>
    <h2 data-typeeffect="SASS"></h2>
    <h2 data-typeeffect="GIT"></h2>
    <h2 data-typeeffect="GITHUB"></h2>
    <h2 data-typeeffect="REACT"></h2>
    <h2></h2>
    <h2></h2>
  </div>
</section>

CodePudding user response:

setTimeout() on every iteration and multiply the index number (current iteration) times the base time set for the timeout().

array.forEach((ele, idx, arr) => {
  setTimeout(() => {
    // actions
  }, idx * 1000); // 1st iteration 0, 2nd 1000, 3rd 2000, 4th 3000, etc
});

The example below features a function that's a bit more reusable, the array, the element to replicate, the initial time, and the element to append to are parameters.

const header = document.querySelector('header');
const skillSet = ["HTML", "CSS", "JAVASCRIPT", "SASS", "GIT", "GITHUB", "REACT"];

function delayed(array, tag, time, node = document.body) {
  array.forEach((txt, idx, arr) => {
    setTimeout(() => {
      const element = document.createElement(tag);
      node.append(element);
      typeEffect(element, arr[idx]);
    }, idx * time);
  });
}

function typeEffect(element, str) {
  let i = 0;
  let interval = setInterval(() => {
    let typedLetters = str.slice(0, i);
    element.textContent = typedLetters;
    if (i === element.length) {
      clearInterval(interval);
    }
    i  ;
  }, 120);
}

delayed(skillSet, 'h2', 1000, header);
<header></header>

  • Related