Creating a hover triggered text weight animation in Next Js / React - with source code

We will be creating a NextJs component that receives a string as a prop & animates it, Code below ๐Ÿ‘‡

Note: Use Variable fonts to get seamless animation. Mona Sans is used for this example

As a bonus, We will be using Framer motion & Tailwind CSS to trigger a letter-by-letter slide-in animation

Animated Text Component Structure

  1. Letters Reference: The lettersRef is created using the useRef hook to store references to the individual letter elements in the text.

  2. Mouse Interaction Effect: An useEffect hook is used to set up a mouse movement interaction effect. It listens for mouse movements to change the font properties of each letter based on their proximity to the mouse cursor.

    • The handleMouseMove function calculates the proximity of each letter to the mouse and modifies font properties such as fontWeight and fontVariationSettings accordingly.

    • The effect adds a mousemove event listener to trigger the interaction and remove it when the component unmounts.

  3. Rendering Animated Text: The component renders the animated text.

    • We split the input text into an array of individual letters and maps over them.

    • Each letter is wrapped in a motion.h1 element, making use of Framer Motion for animation.

    • The animation includes initial opacity and y-position, and a delay is applied to each letter for a staggered entrance effect.

    • The exit transition is defined to control how the letters disappear while it gets unmounted

  4. Export: The AnimatedText component is exported for use in other parts of the application.

    "use client";  // NextJs will need this, React users feel free to  remove this

    import React, { useRef, useEffect } from "react";
    import { motion, AnimatePresence} from "framer-motion";

    const AnimatedText = ({ text }) => {
        const lettersRef = useRef([]);  // Create a reference for the letters

        useEffect(() => {
            // This effect sets up mouse movement interaction for the letters.
            const handleMouseMove = (e) => {
                // Handle mouse movement to change font properties based on proximity to the mouse.
                lettersRef.current.forEach((letter, index) => {
                    if (letter) {
                        const rect = letter.getBoundingClientRect();
                        const dx = e.clientX - (rect.left + rect.width / 2);
                        const dy = e.clientY - (rect.top + rect.height / 2);
                        const distance = Math.sqrt(dx * dx + dy * dy);
                        const maxDistance = 150;  // Maximum distance for interaction (feel free adjust this).
                        const proximity = Math.max(0, maxDistance - distance) / maxDistance;

                        // Modify the font properties based on proximity to the mouse.
                        letter.style.fontWeight = `${300 + proximity * 1000}`;
                        letter.style.fontVariationSettings = `"wdth" ${20 * proximity + 100}`;
                    }
                });
            };

            // Add a mousemove event listener to trigger the interaction.
            window.addEventListener("mousemove", handleMouseMove);

            // Clean up the event listener when the component unmounts.
            return () => {
                window.removeEventListener("mousemove", handleMouseMove);
            };
        }, []);

        return (
            <AnimatePresence mode='wait'>
                {text.split("").map((letter, index) => (
                    <motion.h1
                        key={index}
                        initial={{ opacity: 0, y: 30 }}
                        animate={{
                            opacity: 1,
                            y: 0,
                            transition: { duration: 0.8, delay: index * 0.1 }, {/*creating a staggered slide-in effect*/}
                        }}
                        exit={{ opacity: 0 }}
                        transition={{ duration: 0.3 }}
                        className="inline-block transition-all duration-100 delay-[-30ms] whitespace-nowrap"
                        ref={(el) => (lettersRef.current[index] = el)}
                    >
                        {letter} 
                    </motion.h1>
                ))}
            </AnimatePresence>
        );
    };

    export default AnimatedText;

Usage

import AnimatedText from '../path-to-the-component'
const App = () => {
    return(
        <main>
            <AnimatedText text={'Devignx Studio'}/>
        </main>
    )
}

Thanks for visiting this blog, Feel free to reach me for creating Modern Interactive Web development projects, Visit my portfolio - Hariprasad and my Design agency

6
Subscribe to my newsletter

Read articles from Hari Prasad Designer directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hari Prasad Designer
Hari Prasad Designer

Artist, Designer & Developer