Tips for effectively triggering an animation only once when the element is in view using React

I'm just starting out with React and I want to trigger a CSS @keyframes animation when my element enters the viewport.

To achieve this, I am using the Intersection Observer with the help of the useOnScreen hook from

Here is my JSX :

import React, {useEffect, useRef, useState} from 'react'

import './landing.scss'

function useOnScreen(options){
    const ref = React.useRef(); 
    const [visible, setVisible] = React.useState(false);

    React.useEffect(() => {
        const observer = new IntersectionObserver(([entry]) => {
            setVisible(entry.isIntersecting);
        }, options);

        if (ref.current) {
            observer.observe(ref.current);
        }

        return () => {
            if (ref.current) {
                observer.unobserve(ref.current);
            }
        }

    }, [ref, options])

    return [ref, visible];
}

const Landing = () => {
    const [ref, visible] = useOnScreen();

    return(
        <>
        <div className="titleBox">
            <h2 style={{ animationDuration: `0.5s`, animationIterationCount: 1, animationName: visible ? "showTopText" : ``, animationDelay: "0.3s", animationTimingFunction: "ease" }}>My text</h2>
        </div>
        </>
    )
}

My scss :

@keyframes showTopText {
    0% {top: 100%;}
    100% {top: 0;}
}

.titleBox{
    width: 100%;
    height: 64px;
    overflow: hidden;
    position: relative;

    h2{
        font-size: 56px;
        line-height: 64px;
        width: 100%;
        font-weight: normal;
        position: absolute;
    }
}

The challenge I am facing is that the animation repeats each time the element re-enters the screen, and also my text appears at top: 0%; and disappears to top: 100%; when the animation starts.

Any suggestions on how to solve this issue?

Answer №1

I stumbled upon a solution on Stack Overflow that finally enabled me to achieve my desired outcome. Utilizing react-in-viewport, I managed to create something similar to the following:

const TitleSection2 = (props) => {
    const { inViewport, forwardedRef, enterCount } = props;
    if (inViewport && enterCount === 1) {
        return (
            <div ref={forwardedRef} className="titleBoxSection2">
                <div className="line1">
                    <h2 className="visible">First line</h2>
                </div>
                <div className="line2">
                    <h2 className="visible">second line.</h2>
                </div>
            </div>
        )
    }
    return (
        <div ref={forwardedRef} className="titleBoxSection2">
            <div className="line1">
                <h2 className="notVisible">First line</h2>
            </div>
            <div className="line2">
                <h2 className="notVisible">second line.</h2>
            </div>
        </div>
    );
}

const ViewportTitleSection2 = handleViewport(TitleSection2);

An example of SCSS styling for this component is demonstrated below:

.titleBoxSection2{
    width: 100%;
    height: 128px;
        
    .line1{
        width: 100%;
        height: 64px;
        overflow: hidden;
        position: relative;

        h2.visible{
            font-size: 56px;
            line-height: 64px;
            width: 100%;
            font-weight: normal;
            position: absolute;
            animation: showTopText 0.5s;
            animation-delay: 0.3s;
            animation-fill-mode: forwards;
            animation-timing-function: ease;
            top: 100%;
        }

        h2.notVisible{
            font-size: 56px;
            line-height: 64px;
            width: 100%;
            font-weight: normal;
            position: absolute;
        }
    }

    .line2{
        width: 100%;
        height: 64px;
        overflow: hidden;
        position: relative;

        h2.visible{
            font-size: 56px;
            line-height: 64px;
            width: 100%;
            font-weight: normal;
            position: absolute;
            animation: showTopText 0.5s;
            animation-delay: 0.8s;
            animation-fill-mode: forwards;
            animation-timing-function: ease;
            top: 100%;
        }

        h2.notVisible{
            font-size: 56px;
            line-height: 64px;
            width: 100%;
            font-weight: normal;
            position: absolute;
        }
    }
}

While this approach may not be optimal, it has proven effective for my needs. If you find this helpful or have suggestions for improvement, please feel free to share.

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

How can we align divs in the center horizontally inside a parent div?

This question has been asked many times, but I can't seem to figure it out in my situation as I am new to HTML. I have three divs (boxes: QM, Audit, PM) and I want to center them within a parent div called Services. In the photo provided, you can see ...

Issues arise with Material UI Autocomplete feature where it fails to function properly and ignores input containing commas

Here is an example of their documentation. I want to enable users to select multiple values from the dropdown using commas. When the input is Part 1, everything works fine. However, when the input is Part 1, Part 2, or simply 1,2, the component does not r ...

Running the command "nexrjs create next-app" successfully generates all the necessary directories for the

After trying to install NextJS using both methods npm install next react react-dom and npx create-next-app appname, I noticed that my project directories are different from what they are supposed to look like: Instead of having pages, api(_app.js, index.j ...

What is the method for adding a container in the React Material UI AppBar Component?

When I view MUI's default AppBar, I notice that its children appear quite spread out, especially on wider screens. The logo is positioned all the way to the left, while the other navigation items are too far to the right. As a result, the items seem d ...

The content of one div is encroaching on another div's space

I'm having trouble with my div text overlapping in unexpected ways. The source of the text is a JS file, so it may not appear correctly here. The text in the div.subheader class keeps overlapping into the div.resource class, which is causing layout i ...

Overflowing CSS grid issue

I've been working on this for a couple of hours, but I haven't been able to get it right. I want to add labels in a grid layout with a maximum of 3 columns. If the items don't fit, they should wrap to the next row. It would be great if the i ...

Rotate Chevron icon downwards on mobile devices in a Multilevel Dropdown Bootstrap 4

Bootstrap 4 is being utilized for my current project, I am looking to modify the rotation of the chevron icon in a multi-level dropdown menu specifically on mobile devices when the parent link is tapped, Here is the code snippet for the multi-level dropd ...

What is the reason behind individuals using { " " } in React / JSX code?

In my exploration of React tutorials and videos, I've noticed a common practice where individuals insert { " " } within their JSX templates. I'm curious about the reasoning behind this. It appears to serve as a substitute for line breaks, yet I ...

Display child component automatically upon parent component state update

The main component Dashboard manages the state for each ListItem added to my Watchlist. However, whenever I add an item, it is inserted into the database but only appears when I refresh the browser. class UserDashboard extends React.Component { state = ...

Implementing the useState hooks in an asynchronous callback

I'm currently running an asynchronous operation with a callback function (using IPFS in this scenario, though the specific tool isn't crucial), and am attempting to update state using hooks within that callback. However, for some reason the code ...

Does react version 18 support the Text-to-speech API or react-speech-kit functionality?

Are there any npm packages available that can be utilized in a React project to incorporate text-to-speech functionality into my web application? I am looking to integrate this feature into a designated section of my dashboard to enhance web accessibilit ...

Utilizing the CSS REM unit to define element dimensions

I recently discovered the versatility of using the REM unit for sizing elements, not just font sizes. It works really well in conjunction with the HTML font-size property. html { font-size:1vw } @media all and (max-width:720px) { html { font-size:10px ...

How to prevent text from extending beyond the maximum width limit set in HTML

I defined a max-width of 500px for the div element, but the text inside is overflowing the width instead of wrapping to the next line. Can someone please assist with which code needs to be modified? Here is the code snippet in question: The div is set t ...

Tips for customizing the appearance of the "Read More" button on an Elementor card

I'm new to the world of wordpress and I'm struggling to customize the CSS for a button on a card. My goal is to center the "Read more" button on the post card and give it a border, but I'm not having any luck in Elementor. I tried tweaking t ...

Creating a clickable button within an image container using Bootstrap 5

I am attempting to create a button inside an img element, specifically in the center of that img element within Bootstrap 5. The image is not being used as a background on the grid and I am applying some hover animations to zoom in. I am curious if there ...

Step by Step Guide to Creating Vote Tallying Buttons with XHTML

I'm trying to create a voting system where users can choose between two images. However, all the tutorials I've found focus on text or check-box options. How can I make clickable buttons tally votes for each image and then display the results on ...

Clear Material-UI Autocomplete Selected Value

I'm facing an issue with my React code. Using Material-UI and Redux-Form, I have a select input like and after changing this select, I need to reset the value in . I've tried using the 'change' action from redux-form to set the value ...

What sets apart the placement of these two local variables?

Are there any distinguishable differences between the following two methods of defining local variables? Is it possible for the second method to inadvertently access global scope? I have noticed that multiple components within the application contain const ...

Is the navigation item only clickable within its designated area?

Apologies in advance for the confusion, but I'm facing an issue with the navigation bar on my website. It works perfectly on the Home page when hovered over, but on the login and register pages, it requires hovering over a specific spot under the item ...

Attempting to update state on a component that is no longer mounted

There are many instances in my components where I find myself needing to execute the following code: function handleFormSubmit() { this.setState({loading: true}) someAsyncFunction() .then(() => { return this.props.onSuccess() }) . ...