Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.Js + Framer Motion animate only on initial page load

I am working on a React project where I have components animate in when they scroll in to view. I am using Framer Motion. How can I make it so the animation only triggers the first time you scroll by the component?

Right now, if I scroll down the page the animations work as expected. However, if I refresh or leave the page and come back the animations will fire again. Scrolling to the middle of the page, refreshing, and then scrolling back up will fire animations on components that were scrolled by before.

I understand this is the default behavior of Framer Motion going from initial value to animate value as components remount. I am looking to prevent this behavior on components that where in the users viewport before.

Sample code for one of the components is posted below. Any help is appreciated.

const Banner = ({ title, body, buttonStyle, buttonText, image, switchSide, link }) => {
  const { ref, inView } = useInView({
    threshold: .8
  })
  return (
    <motion.div className="banner" 
      ref={ref}
      initial={{  opacity: 0 }}
      animate={ inView ? {  opacity: 1 } : ''}
      transition={{ duration: .75 }}
    >
      <div className={`container ${switchSide ? 'banner-switch': ''}`}>
        <div className="side-a">
          <img src={ image } />
        </div>
        <div className="side-b">
          <h2>{ title }</h2>
          <p>{ body }</p>
          {
            buttonText
              ? <Button buttonStyle={buttonStyle} link={link} justify="flex-start">{ buttonText }</Button>
              : ''
          }
        </div>
      </div>
    </motion.div>
  )
}
export default Banner
like image 638
Kryptikk21 Avatar asked Mar 02 '23 02:03

Kryptikk21


1 Answers

I was faced with a similar problem lately. I was implementing intro animation and did not want it to trigger on every page refresh, so I have made a custom hook which saves a timestamp in local storage and on each page refresh compares saved time with stored timestamp and fires when the time has passed and stores a new value there. If you want to play it only once you could simply implement customize my code to store boolean and you’re good to go.

My custom hook

import {useEffect} from 'react';

const useIntro = () => {

const storage = window.localStorage;
const currTimestamp = Date.now();
const timestamp = JSON.parse(storage.getItem('timestamp') || '1000');

const timeLimit = 3 * 60 * 60 * 1000; // 3 hours

const hasTimePassed = currTimestamp - timestamp > timeLimit;

useEffect(() => {
    hasTimePassed ? 
        storage.setItem('timestamp', currTimestamp.toString()) 
        : 
        storage.setItem('timestamp', timestamp.toString());
}, []);

return hasTimePassed;
};

export default useIntro;

You would need to make this simple change in your code

const Banner = ({ title, body, buttonStyle, buttonText, image, switchSide, link }) => {
    const showAnimation = useIntro();


  const { ref, inView } = useInView({
    threshold: .8
  })
  return (
    <motion.div className="banner" 
      ref={ref}
      initial={{  opacity: 0 }}
      animate={ inView && showAnimation ? {  opacity: 1 } : ''}
      transition={{ duration: .75 }}
    >
      <div className={`container ${switchSide ? 'banner-switch': ''}`}>
        <div className="side-a">
          <img src={ image } />
        </div>
        <div className="side-b">
          <h2>{ title }</h2>
          <p>{ body }</p>
          {
            buttonText
              ? <Button buttonStyle={buttonStyle} link={link} justify="flex-start">{ buttonText }</Button>
              : ''
          }
        </div>
      </div>
    </motion.div>
  )
}
export default Banner

Hope this is what you were after.

like image 111
indyanin Avatar answered Mar 17 '23 01:03

indyanin