React.Js + Framer Motion анимирует только при начальной загрузке страницы

Я работаю над проектом React, в котором компоненты анимируются, когда они прокручиваются для просмотра. Я использую Framer Motion. Как сделать так, чтобы анимация срабатывала только при первой прокрутке компонента?

Прямо сейчас, если я прокручиваю страницу вниз, анимация работает должным образом. Однако, если я обновлю страницу или уйду со страницы и вернусь, анимация снова сработает. Прокрутка до середины страницы, обновление и последующая прокрутка вверх вызовут анимацию компонентов, которые были прокручены ранее.

Я понимаю, что это поведение Framer Motion по умолчанию, переходящее от начального значения к значению анимации при перемонтировании компонентов. Я хочу предотвратить такое поведение компонентов, которые раньше находились в области просмотра пользователей.

Пример кода для одного из компонентов размещен ниже. Любая помощь приветствуется.

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

person Kryptikk21    schedule 09.09.2020    source источник


Ответы (2)


Недавно я столкнулся с подобной проблемой. Я реализовал вводную анимацию и не хотел, чтобы она запускалась при каждом обновлении страницы, поэтому я сделал настраиваемый хук, который сохраняет временную метку в локальном хранилище и при каждом обновлении страницы сравнивает сохраненное время с сохраненной временной меткой и срабатывает, когда время прошло и сохраняет там новое значение. Если вы хотите сыграть в нее только один раз, вы можете просто реализовать настройку моего кода для хранения логических значений, и все готово.

Мой индивидуальный крючок

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;

Вам нужно будет внести это простое изменение в свой код

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

Надеюсь, это то, что вам нужно.

person indyanin    schedule 10.09.2020
comment
Большое спасибо за это! Это именно то поведение, которое я искал. Пришлось добавить showAnimation к исходному. initial = {{showAnimation? {opacity: 0}: {opacity: 1}}} - person Kryptikk21; 10.09.2020
comment
Я рад, что это сработало для вас. Отметьте мой ответ как решенный, нажав на галочку под стрелками голосования :) - person indyanin; 10.09.2020

Я немного изменил ваш хук, чтобы он мог отслеживать отдельные страницы. Допустим, вы посетили домашнюю страницу, и там сработала анимация, но вы все равно хотите, чтобы анимация запускалась на других страницах.

import {useEffect} from 'react';
import { useLocation } from 'react-router-dom'

export const useIntro = () => {

const location = useLocation()
const urlPath = location.pathname
const storage = window.localStorage;
const currTimestamp = Date.now();
const timestamp = JSON.parse(storage.getItem(`timestamp${urlPath}`) || '1000');

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

const hasTimePassed = currTimestamp - timestamp > timeLimit;

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

return hasTimePassed;
};

export default useIntro;
person Kryptikk21    schedule 10.09.2020
comment
Вау, здорово! Я думаю, что у меня есть хороший пример использования для этого, и я не придумал этого раньше. Спасибо, что поделился! ???? - person indyanin; 10.09.2020