Tips for effectively handling requestAnimationFrame

I created a unique function that both scrambles and translates text. The functionality is smooth if you patiently wait for the animation to finish before moving the mouse over to other elements. However, if you try to rush through to the next one, the previous animation abruptly stops.

const menuItems = document.querySelectorAll("#menu li"),
  menuText = document.querySelectorAll("#menu li a"),
  translations = document.querySelectorAll("#translation li"),
  originalEnglish = [];
originalJapanese = [];

menuText.forEach((e) => {
  originalEnglish.push(e.innerHTML);
});
translations.forEach((e) => {
  originalJapanese.push(e.innerHTML);
});

let target;
let frame = 0;
let frame2 = 0;
let singleText;
let singleText2;

let callbackId;

const glitch = ["-", "+", "=", `[`, `;`, "#", "%", "^", "*", "_"];

let times;

let isRunning = false;

menuItems.forEach((e) => {
  // });
  e.addEventListener("mouseover", () => {
    target = [].slice.call(menuItems).indexOf(e);

    if (!menuItems[target].classList.contains("translated")) {
      menuItems[target].classList.add("translated");

      singleText = originalEnglish[target].split("");
      times = singleText.length * 2;
      frame = 0;

      function glitchTranslation() {
        if (frame < times + translations[target].innerHTML.length) {
          if (frame < times) {
            singleText[Math.floor(Math.random() * singleText.length)] = glitch[Math.floor(Math.random() * 10)];
            menuText[target].innerHTML = singleText.join("");
          } else {
            singleText[frame - times] = translations[target].innerHTML[frame - times];

            menuText[target].innerHTML = singleText.join("").slice(0, translations[target].innerHTML.length);
          }
          isRunning = true;
          callbackId = requestAnimationFrame(glitchTranslation);
        } else if (frame === times + translations[target].innerHTML.length) {
          isRunning = false;
        }
        frame++;
      }
      glitchTranslation();
    }
  });
});
#menu {
  display: flex;
  flex-direction: column;
}

#menu li {
  margin: 20px 0;
  display: inline-block;
  width: 150px;
  border: 1px solid pink;
}
<header>
  <nav>
    <ul id="menu">
      <li id="trigger1"><a href="#">something</a></li>
      <li id="trigger3"><a href="#">earth</a><span class="bar"></span></li>
      <li id="trigger4">
        <a href="about.html">human</a><span class="bar"></span>
      </li>
      <li id="trigger6">
        <a href="#">contact</a><span class="bar"></span>
      </li>
    </ul>

    <ul id="translation" style="display: none">
      <li>なにか</li>
      <li>地球</li>
      <li>人間</li>
      <li>お問い合わせ</li>
    </ul>
  </nav>
</header>
<main></main>
<footer></footer>

Answer №1

To ensure that your variable declarations are scoped correctly and not overridden by the next event, it is recommended to move them inside the mouseover handler:

const menuItems = document.querySelectorAll("#menu li"),
  menuItemText = document.querySelectorAll("#menu li a"),
  translations = document.querySelectorAll("#translation li"),
  originalEnglishText = [],
  originalJapaneseText = [];

menuItemText.forEach((item) => {
  originalEnglishText.push(item.innerHTML);
});
translations.forEach((item) => {
  originalJapaneseText.push(item.innerHTML);
});

const specialChars = ["-", "+", "=", `[`, `;`, "#", "%", "^", "*", "_"];

menuItems.forEach((item) => {
  // });
  item.addEventListener("mouseover", () => {

    // place all variable declarations inside the mouseover handler
    let targetIndex;
    let frameCount = 0;
    let frame2Count = 0;
    let textItem;
    let textItem2;

    let callbackId;
    let iterations;

    let running = false;


    targetIndex = [].slice.call(menuItems).indexOf(item);

    if (!menuItems[targetIndex].classList.contains("translated")) {
      menuItems[targetIndex].classList.add("translated");

      textItem = originalEnglishText[targetIndex].split("");
      iterations = textItem.length * 2;
      frameCount = 0;

      function applyGlitchEffect() {
        if (frameCount < iterations + translations[targetIndex].innerHTML.length) {
          if (frameCount < iterations) {
            textItem[Math.floor(Math.random() * textItem.length)] =
              specialChars[Math.floor(Math.random() * 10)];
            menuItemText[targetIndex].innerHTML = textItem.join("");
          } else {
            textItem[frameCount - iterations] =
              translations[targetIndex].innerHTML[frameCount - iterations];

            menuItemText[targetIndex].innerHTML = textItem
              .join("")
              .slice(0, translations[targetIndex].innerHTML.length);
          }
          running = true;
          callbackId = requestAnimationFrame(applyGlitchEffect);
        } else if (frameCount === iterations + translations[targetIndex].innerHTML.length) {
          running = false;
        }
        frameCount++;
      }
      applyGlitchEffect();
    }
  });
});
#menu {
  display: flex;
  flex-direction: column;
}

#menu li {
  margin: 20px 0;
  display: inline-block;
  width: 150px;
  border: 1px solid pink;
}
<header>
  <nav>
    <ul id="menu">
      <li id="trigger1"><a href="#">something</a></li>
      <li id="trigger3"><a href="#">earth</a><span class="bar"></span></li>
      <li id="trigger4">
        <a href="about.html">human</a><span class="bar"></span>
      </li>
      <li id="trigger6">
        <a href="#">contact</a><span class="bar"></span>
      </li>
    </ul>

    <ul id="translation" style="display: none">
      <li>なにか</li>
      <li>地球</li>
      <li>人間</li>
      <li>お問い合わせ</li>
    </ul>
  </nav>
</header>
<main></main>
<footer></footer>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The capability to scroll within a stationary container

Whenever you click a button, a div slides out from the left by 100%. This div contains the menu for my website. The problem I'm encountering is that on smaller browser sizes, some of the links are hidden because they get covered up. The #slidingMenu ...

Converting TypeScript to ES5 in Angular 2: A Comprehensive Guide

I am currently diving into Angular 2 and delving into Typescript to create simple applications within the Angular 2 framework. What I have discovered is that with Typescript, we can utilize classes, interfaces, modules, and more to enhance our application ...

What seems to be causing the malfunction in this ranking function?

I created a custom function to retrieve the top five users from a JSON database. Below is the code snippet: var users = []; Object.keys(database).forEach(user => { if (!users[0]) { users[0] = user } else { var checked = false; for (let ...

Is it possible to execute in a specific context using npm?

I am seeking to execute npm scripts that are executable by VuePress. For instance, I have VuePress installed and would like to run the command vuepress eject. Although I can access vuepress in my scripts, there is no specific script for eject: "scr ...

Updating View in Angular 2 ngClass Issue

I'm encountering some challenges with updating my view using ngClass despite the back end updating correctly. Below is my controller: @Component({ selector: 'show-hide-table', template: ' <table> <thead> ...

How come the sentence array is displaying 38 elements even though it's supposed to only accommodate 30?

#include <iostream> #include <vector> #include <string> #include <cstring> using namespace std; int main() { //initialize a char array and a vector char sentence[30] = {}; vector<string> words{"The", "on ...

Utilizing a factory as the data source in angular-datatables leads to unexpected errors

Is it possible to use a factory as a source data in angular-datatables? Basically, I want to retrieve data in a variable and use it as the data source. UPDATED (06/22/2016) This is my updated factory: statisticsModule.factory('globalFactory&apos ...

I am unable to give back an item

I am working with an object structure that looks like this: const obj = { name: 'john', children: [ { name: 'Foo' }, { name: 'Bar', children: [ { name: 'Doe' ...

"Trouble encountered while trying to display Angular in an HTML document

In my Angular project, there is a feature where a list of orders is displayed in one view. Each order is represented by its title only. When the user clicks on the title, they should be redirected to a new view showing the complete content of that particul ...

Creating a three-row CSS layout where the middle row aligns to the right side

I am working on developing a mobile device layout with 3 blocks. The first and third blocks contain text fields, while the second block is filled with a map. However, all of my blocks don't look good when they are too wide. The browser window can have ...

Using jQuery to extract a specific portion of a URL path

Seeking assistance with extracting category information from lengthy URLs and assigning it to a variable. Consider the following URL: http://example.com/community/home/whatever.html The goal is to assign the variable to the folder path that follows /hom ...

How can I ensure security measures are in place to avoid XSS attacks on user-generated HTML content?

Currently, I am in the process of developing a web application that will allow users to upload entire web pages onto my platform. My initial thought was to utilize HTML Purifier from http://htmlpurifier.org/, but I am hesitant because this tool alters the ...

Make sure to always select the alternative option in ajax

I am trying to create a condition where if the value of id=type_investor is either 1 or 6, an email should be sent using a different controller. Here is my complete code: function (isConfirm) { if (!isConfirm) return; $.ajax({ ...

All outcomes being displayed from Youtube's json feed

Currently, I am retrieving a youtube playlist by using the following link: I'm curious if there is a method to display all 250 videos from my feed instead of just the 25 that are currently being shown. Any assistance on this matter would be highly a ...

Adding numerous objects to a Vuex store using mutations

I am currently working with the following store setup: import Vue from 'vue' import Vuex from 'vuex' import axios from 'axios' Vue.use(Vuex) export default new Vuex.Store({ plugins: [createPersistedState()], state: { ...

The method WebKitBrowser.StringByEvaluatingJavaScriptFromString does not provide any output

After running all JavaScript, I need to retrieve the HTML content. However, I am facing an issue using WebKit.NET where the method WebKitBrowser.StringByEvaluatingJavaScriptFromString does not return anything, even with a simple alert(). When I try passi ...

What is the best way to locate an item reference for a specific DOM element?

Imagine a vast DOM structure with approximately 10,000 HTML elements, including 1,000 span tags. None of the span elements have any identifiers and are buried deep within other objects with long, complex Xpath paths that are not user-friendly for selectio ...

Ways to identify the visible elements on a webpage using JavaScript

I'm working on a nextjs app , I am looking to dynamically update the active button in the navbar based on which section is currently visible on the page. The child elements of the page are structured like this: <div id="section1" > < ...

Retrieve data points from ol.layer.Vector using OpenLayers 4

Having recently started using OpenLayers, I find myself in a state of confusion. I'm attempting to retrieve all features from a kml vector layer, but have been unsuccessful thus far. It perplexes me as to what mistake I might be making. Below is the ...

Is there a way to create a PHP function that can process an AJAX request and return a boolean value?

After some testing, I discovered that when the code snippet below is executed within my PHP function to manage an AJAX call: session_start(); if ( $_POST['animal'] != $_SESSION['animal'] ) die(json_encode(false)); Upon returning to th ...