React中如何在元素进入视口时正确触发单次CSS动画
解决方案
问题原因
- 动画重复触发:当前
useOnScreen钩子会在元素离开视口时把visible重置为false,再次进入时又会设为true,相当于重新给元素绑定动画属性,导致动画反复执行。 - 初始状态跳动:你的h2元素没有设置初始
top值,默认是0,动画触发时第一帧会跳转到@keyframes定义的top: 100%才开始执行动画,就出现了先消失再滑入的异常效果。同时缺少animation-fill-mode: forwards配置,动画结束后会回到初始样式。
修复后代码
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(prevState => prevState || 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( <> {/* 给监听元素绑定ref,保证Intersection Observer正常工作 */} <div className="titleBox" ref={ref}> <h2 style={{ animationDuration: `0.5s`, animationIterationCount: 1, animationName: visible ? "showTopText" : `none`, animationDelay: "0.3s", animationTimingFunction: "ease", // 动画结束后保留最后一帧样式 animationFillMode: 'forwards' }}>My text</h2> </div> </> ) }
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; // 设置初始top值和动画起始状态一致,避免跳动 top: 100%; } }
核心修改说明
- 修改
useOnScreen的状态更新逻辑,visible一旦变为true就不再重置,保证动画只触发一次。 - 给目标元素绑定
ref,保证Intersection Observer能正常监听元素的可见状态。 - SCSS中给h2添加初始
top: 100%,和动画起始状态对齐,避免动画触发时的跳动。 - 新增
animationFillMode: 'forwards'配置,动画结束后保持top: 0的最终状态,不会回到初始的top: 100%。
内容的提问来源于stack exchange,提问作者Hugo Barbé
相关产品推荐
相关产品推荐

