【问题标题】:React.Js + Framer Motion animate only on initial page loadReact.Js + Framer Motion 仅在初始页面加载时动画
【发布时间】:2020-12-28 06:02:00
【问题描述】:

我正在开发一个 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

【问题讨论】:

    标签: reactjs animation framer-motion


    【解决方案1】:

    我最近遇到了类似的问题。我正在实现介绍动画并且不希望它在每次页面刷新时触发,所以我制作了一个自定义挂钩,它将时间戳保存在本地存储中,并且在每次页面刷新时将保存的时间与存储的时间戳进行比较,并在时间过去时触发在那里存储一个新值。如果你只想玩一次,你可以简单地实现自定义我的代码来存储布尔值,你就可以开始了。

    我的自定义钩子

    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

    希望这就是你所追求的。

    【讨论】:

    • 非常感谢!这正是我正在寻找的行为。必须将 showAnimation 添加到初始值。初始={{ 显示动画? {不透明度:0}:{不透明度:1}}}
    • 我很高兴它对你有用。请点击投票箭头下方的复选标记将我的答案标记为已解决:)
    【解决方案2】:

    我对你的钩子做了一个小改动,以便它可以跟踪单独的页面。假设您已经访问了主页并且动画已经在那里触发,但您仍然希望动画在其他页面上触发。

    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;
    

    【讨论】:

    • 哇太棒了!我想我可能对此有很好的用例,但之前没有想出这个。感谢分享! ?
    【解决方案3】:

    有一个 API - 交叉点观察者 API。还有一个 React 钩子可以使用它——react-intersection-observer。我现在在一个项目中使用它 - 在这里我将它提取为自定义钩子

    const useHasBeenViewed = () => {
      const [ref, inView] = useInView();
      const prevInView = useRef(false);
      const hasBeenViewed = prevInView.current || inView;
      useEffect(() => {
        prevInView.current = inView;
      });
      
      return [hasBeenViewed, ref];
    }
    

    正在使用中

    const App = () => {
      const [hasBeenViewed, ref] = useHasBeenViewed();
      return (
        <motion.div animate={{opacity: hasBeenViewed ? 1 : 0}} ref={ref}>
          {hasBeenViewed}
        </div>
      );
    }
    

    当交叉点观察者 API 仅用于此时,时间戳答案对我来说似乎是一种不优雅的解决方法。

    【讨论】:

      猜你喜欢
      • 2022-10-01
      • 2022-11-01
      • 2022-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-10
      • 2022-01-15
      • 1970-01-01
      相关资源
      最近更新 更多