React.Js + Framer Motion 仅在初始页面加载时设置动画
React.Js + Framer Motion animate only on initial page load
我正在做一个 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
我最近遇到了类似的问题。我正在实现介绍动画并且不希望它在每次页面刷新时触发,所以我制作了一个自定义挂钩,它在本地存储中保存时间戳,并且在每次页面刷新时将保存的时间与存储的时间戳进行比较,并在时间过去时触发在那里存储一个新值。如果你只想玩一次,你可以简单地实现自定义我的代码来存储布尔值,你就可以开始了。
我的自定义挂钩
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
希望这就是您想要的。
我对你的挂钩做了一个小改动,以便它可以跟踪不同的页面。假设您已经访问了主页并且动画已经在那里触发,但您仍然希望动画在其他页面上触发。
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;
有一个 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 只是为了这个时,时间戳答案对我来说似乎是一个不优雅的解决方法。
Framer motion 现在内置了对此的支持。检查这个 link:
https://codesandbox.io/s/framer-motion-animate-in-view-5-3-94j13
相关代码:
function FadeInWhenVisible({ children }) {
return (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.3 }}
variants={{
visible: { opacity: 1, scale: 1 },
hidden: { opacity: 0, scale: 0 }
}}
>
{children}
</motion.div>
);
}
用法:
<FadeInWhenVisible>
<Box />
</FadeInWhenVisible>
这可以确保动画仅在元素进入视口时出现一次。我们不再使用 react-intersection-observer 检查元素是否在视口中!
我正在做一个 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
我最近遇到了类似的问题。我正在实现介绍动画并且不希望它在每次页面刷新时触发,所以我制作了一个自定义挂钩,它在本地存储中保存时间戳,并且在每次页面刷新时将保存的时间与存储的时间戳进行比较,并在时间过去时触发在那里存储一个新值。如果你只想玩一次,你可以简单地实现自定义我的代码来存储布尔值,你就可以开始了。
我的自定义挂钩
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
希望这就是您想要的。
我对你的挂钩做了一个小改动,以便它可以跟踪不同的页面。假设您已经访问了主页并且动画已经在那里触发,但您仍然希望动画在其他页面上触发。
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;
有一个 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 只是为了这个时,时间戳答案对我来说似乎是一个不优雅的解决方法。
Framer motion 现在内置了对此的支持。检查这个 link:
https://codesandbox.io/s/framer-motion-animate-in-view-5-3-94j13
相关代码:
function FadeInWhenVisible({ children }) {
return (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.3 }}
variants={{
visible: { opacity: 1, scale: 1 },
hidden: { opacity: 0, scale: 0 }
}}
>
{children}
</motion.div>
);
}
用法:
<FadeInWhenVisible>
<Box />
</FadeInWhenVisible>
这可以确保动画仅在元素进入视口时出现一次。我们不再使用 react-intersection-observer 检查元素是否在视口中!