logo-header

React 'Hack' Link Animation

Header

October, 18, 2023 by Andrew Farmer in Web Development


A while ago I was tasked by a client to create a client website to advertise for their up and coming video game, Fox and Shadow.



The game's narrative unfolds in a city overrun by AI, where remnants of humanity survive in cryosleep underground, leaving a handful of scavengers to brave the polluted surface. This dystopian setting, with its cyberpunk, electronic vibe, sparked an idea to translate the game's essence into a dynamic user journey on the website.

 

The Concept: Randomized ‘Hacking’ Animation

Intrigued by the creativity of YouTube front-end developers, I envisioned a randomized 'hacking' animation for hyperlinks. This animation, seamlessly integrated into the Fox and Shadow website, needed to meet specific criteria:

Hover Interaction: When users hover over a link, the animation should play without disrupting the page layout.

Continuous Animation: The animation should persist even when the user stops hovering, creating an immersive experience.

Non-Restartable Cycle: If a user hovers over a link again before the previous cycle finishes, the animation shouldn't restart, ensuring a smooth user experience.

Single Playback: The animation should play only once during the hover event, irrespective of whether the user re-enters the event.An additional goal is to make the code easily scalable and reusable in other links.

 

Technical Requirements for React

Since we are using React, we need to set up a few important steps.

Router Configuration

Integrate the <Router> component from 'react-router-dom' to establish the context for <Link> components, enabling seamless navigation within the underlying routing system.

import { BrowserRouter as Router } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>
);

The Router component is important for setting up the context that allows <Link> to create navigation links that work with underlying routing systems. Note that <Link> doesn’t work with external links or href tags, but we will look at adapting that into our code later.

 

Animated Link Component

Now let’s create an animated link component that only generates the link but also manages the animation independently.

By encapsulating the link and animation logic into a dedicated component, we enhance the maintainability and scalability of our code. This approach allows us to seamlessly incorporate animated links across various sections of our application without duplicating code or sacrificing clarity.

Creating a separate component for the animated link not only adheres to best practices in React development but also aligns with the broader goal of crafting a robust, flexible, and maintainable codebase

So, let's dive into the nitty-gritty of coding this reusable animated link.

// Import necessary dependencies from React and 'react-router-dom'
import { useState } from 'react'; // component to manage state
import { Link } from 'react-router-dom'; //link from react-router-dom

// Import the AnimationHackEffect component from './animationHack.js'
import AnimationHackEffect from './animationHack.js'; // this will be our animation

// Define a functional component named AnimationLink, receiving props as parameters
const AnimationLink = ({ targetId, to, dataValue, icon, onMouseEnter, onMouseLeave, isExternal }) => {
// State variables to track animation and hover states
const [isAnimating, setIsAnimating] = useState(false);
const [isHovered, setIsHovered] = useState(false);

//targetId = css ID that will be changing.
//to = the hyperlink that will be passed to the user
//dataValue = the text to pass to the animation so it knows what to make/return to.
//Icon = if you would like to use an <i> icon before the text animation. Here I am using a Font Awesome icon.
//onMouseEnter = what to do when the mouse enters the area of text/css
//onMouseLeave = what to do when the mouse leaves the area of text/css
//isExternal = true/false Boolean to tell the component to use <Link> or <a> 

// Function to handle mouse enter event
  const handleMouseEnter = () => {
    setIsHovered(true); //set hovered state
    setIsAnimating(true); //set animation state
    // Call the provided onMouseEnter function if it exists
    if (onMouseEnter) {
      onMouseEnter();
    }
  };

// Function to handle mouse leave event
  const handleMouseLeave = () => {
    setIsHovered(false);
  };

// Conditional rendering based on whether the link is external or internal
  if (isExternal) {
    // External links
    return (
      <div className=link'
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        <a
          href={to} // href for external links
        >
          <i className={icon}></i>&nbsp;
        </a>
        <a
          id={targetId} //css target ID
          href={to} // href for external links
          className='papercactus__socials-social_icon'
        >
          {dataValue} //the datavalue previously mentioned that is also passed to the animation component
//below is the AnimationHackEffect Component, this component handles all the animation logic.
          <AnimationHackEffect
            targetElementId={targetId} //css target ID
            dataValue={dataValue}// text value that users see which is passed to the animation component
            isAnimating={isAnimating} //animation is true
            setIsAnimating={setIsAnimating} //setter for animation is true
          />
        </a>
      </div>
    );
  } else {
// Internal links used by React Router
    return (
      <div className='link'
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        <Link
          to={to}
        >
          <i className={icon}></i>&nbsp;
        </Link>
        <Link
          id={targetId}
          data-value={dataValue}
          className='papercactus__socials-social_icon'
          to={to}
        >
// the data value previously mentioned that is also passed to the animation component.
          {dataValue} 
          <AnimationHackEffect
            targetElementId={targetId} //css target ID
            dataValue={dataValue} //text value that is passed to the animation
            isAnimating={isAnimating} //animation is true
            setIsAnimating={setIsAnimating} //setter for animation is true
          />
        </Link>
      </div>
    );
  }
};

// Export the AnimationLink component as the default export
export default AnimationLink;

 

The AnimationLink component takes charge of generating both <Link> components and <a> tags, effectively acting as a bridge between the structure of our application and the animation logic handled by the AnimationHack component.

To ensure the seamless integration of the animation logic, a crucial decision is made using an if statement. This decision-making process evaluates the value of {isExternal}—a prop provided to the AnimationLink component. If {isExternal} is true, it signals to React that an <a> tag should be rendered.

 

Hack Animation Component

The animation component is the heartbeat of this project, injecting life into the static world of links. Let's craft a component that orchestrates the mesmerizing logic of our animation. In this pivotal component, we're navigating the realm of text randomization, an intricate dance set to the rhythm of intervals. Here's how we bring this animation logic to life; which updates at intervals of 30 milliseconds for 3 loops for each character that is passed down from the AnimationLinks {dataValue}.

// Import necessary hooks from the 'react' library
import { useEffect, useRef } from 'react';

// Define a functional component named 'AnimationHackEffect' that takes in props
const AnimationHackEffect = ({ targetElementId, dataValue, isAnimating, setIsAnimating }) => {
  // Define the alphabet for random letter generation
  const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  // Create refs to persist values across renders
  const interval = useRef(null);
  const iteration = useRef(0);

// targetElementId = css ID that will be changing.
//dataValue = the text to pass to the animation so it knows what to  make/return to.
//Icon = if you would like to use an <i> icon before the text animation. 
// isAnimating = // true/false Boolean if its animating or not
// setIsAnimating = //setter for isAnimating


  // useEffect hook: runs after every render, mimicking component lifecycle methods
  useEffect(() => {
    // Retrieve the target element using the provided ID
    const targetElement = document.getElementById(targetElementId);

    // test if the target element exists, if doesn’t exist then end.
    if (!targetElement) {
      console.error(`Element with ID ${targetElementId} not found`);
      return; // Exit the function if the element is not found
    }

    // Check if animation is active, if true, begin animation sequence, one letter at a time
    if (isAnimating) {
      // Set up an interval to create the animation effect
      interval.current = setInterval(() => {
        // Modify the text content of the target element dynamically
        targetElement.innerText = targetElement.innerText
          .split("")
          .map((letter, index) => {
            // If the current index is less than the current iteration value
            if (index < iteration.current) {
              // Return the corresponding letter from the original dataValue
              return dataValue[index];
            }

            // Otherwise, return a random letter from the alphabet
            return letters[Math.floor(Math.random() * 26)];
          })
          .join("");

        // Check if the iteration has reached the length of the original dataValue
        if (iteration.current >= dataValue.length) {
          // If so, clear the interval, reset the iteration, and signal the end of animation
          clearInterval(interval.current);
          iteration.current = 0;
          setIsAnimating(false);
        }

        // Increment the iteration by 1/3 to control the animation speed
        iteration.current += 1 / 3;
      }, 30); // Run the interval every 30 milliseconds
    }

    // Cleanup function. Clear the interval when the component is unmounted or the dependencies change
    return () => {
      clearInterval(interval.current);
    };
  }, [targetElementId, dataValue, isAnimating, setIsAnimating]); // Dependency array for useEffect

  // The component doesn't render anything (returns null)
  return null;
};

// Export the AnimationHackEffect component as the default export
export default AnimationHackEffect;

 

When both components come together and call an <AnimationLink> on our App component.

 

<AnimationLink
  targetId={'appHackLink'}
  to={''}
  dataValue={'Click here to connect to the cyberspace'}
/> 

 

You can see the effect in motion.

 


Remember, the key is to align with your design and theme—mirroring the same nomenclature as your IDs. Using CSS, you can really bring the animation effect into the cyberspace.

As this effect comes to life, it's worth noting that the intensity might be more pronounced with longer text. Yet, with shorter text snippets, and even when adorned with an icon, it takes on a distinct ‘cyber’ vibe.


Feel free to use this code in your own projects, or modify it to your own desires.The github page can be found here.

To witness this dynamic effect firsthand, navigate to the Fox and Shadow homepage. You'll be immersed in the live showcase of these components, turning every link into a cyber-compromised link to the post-apocalyptic landscape of Fox and Shadow.

Seeding a Production Postgresql Database with Capistrano

Previous

The Art of Articulation

Next