React父组件状态变化触发SectionHeader子组件重复动画如何解决?
问题原因
- 每次状态变更触发头部动画:你在渲染字母span时使用
nanoid()作为key,每次组件重渲染都会生成全新的key,React会销毁原有span节点、重新创建新节点,新节点自然会重新触发动画。同时你自定义的useIntersectionHook缺少清理逻辑,也可能导致重复监听的问题。 - 括号伪元素无动画:你的CSS仅为
.letter类定义了可视状态下的动画规则,没有给伪元素::before、::after添加可视状态对应的动画触发逻辑。
修复方案
1. 修复动画重复触发问题
调整SectionHeader组件
- 替换不稳定的
nanoid()key为数组索引(头部文本固定,索引是稳定值) - 用
React.memo包裹组件避免不必要重渲染
// 调整后的SectionHeader const SectionHeader = React.memo(({ children }) => { const headerRef = useIntersection( styles.sectionHeader__isVisible, { rootMargin: '0px 0px -100px 0px', threshold: 1 }, [children] ); const textToLetters = children.split('').map((letter, idx) => { const style = !letter ? styles.space : styles.letter; return ( <span className={style} key={idx}> {letter} </span> ); }); return ( <div className={styles.sectionHeader_wrapper} ref={headerRef}> <h2 className={styles.sectionHeader}>{textToLetters}</h2> </div> ); });
完善useIntersection Hook
- 添加observer清理逻辑,避免内存泄漏和重复监听
- 补全useEffect依赖项,避免闭包问题
const useIntersection = ( activeClass, { root = null, rootMargin = '0px', threshold = 1 }, dependency = [], unobserveAfterFirstIntersection = true ) => { const elementRef = useRef(null); useEffect(() => { const options = { root, rootMargin, threshold }; const observer = new IntersectionObserver((entries, observerObj) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add(activeClass); unobserveAfterFirstIntersection && observerObj.unobserve(entry.target); } else if (!unobserveAfterFirstIntersection) { entry.target.classList.remove(activeClass); } }); }, options); const currentEl = elementRef.current; currentEl && observer.observe(currentEl); // 新增清理逻辑 return () => { currentEl && observer.unobserve(currentEl); observer.disconnect(); }; // 补全所有依赖项 }, [activeClass, root, rootMargin, threshold, unobserveAfterFirstIntersection, ...dependency]); return elementRef; };
2. 修复伪元素无动画问题
在CSS中为可视状态下的伪元素添加动画规则,示例如下:
/* 新增伪元素动画触发规则 */ .sectionHeader__isVisible::before { animation: bracketLeftIn .3s ease-out forwards; } .sectionHeader__isVisible::after { animation: bracketRightIn .3s ease-out forwards .15s; } /* 自定义伪元素动画,可根据实际需求调整参数 */ @keyframes bracketLeftIn { from { opacity: 0; transform: translateX(-15px); } to { opacity: 1; transform: translateX(0); } } @keyframes bracketRightIn { from { opacity: 0; transform: translateX(15px); } to { opacity: 1; transform: translateX(0); } }
内容的提问来源于stack exchange,提问作者ann.piv
相关产品推荐
相关产品推荐

