滚动时重叠图片缩放动画实现问题(React+TypeScript)
滚动重叠图片动画问题修复方案
我想要实现滚动时重叠图片的动画效果:向下滚动时图片放大,向上滚动时缩小。已知需要用Intersection Observer API处理滚动逻辑,尝试实现但未成功。技术栈是React+TypeScript,使用内联样式开发,参考了三重叠图片滚动动画示例及Codepen实现。
原实现代码(OverlappingImages.tsx)
import React from 'react'; const styles = { container: { position: 'relative', height: '400px', margin: '0 50px', div: { width: '380px', border: '1px solid #000', overflow: 'hidden', lineHeight: 0, transition: 'transform .4s ease-in-out', img: { width: '100%', fontSize: 0, }, }, img1: { left: '5%', top: 0, position: 'absolute', transform: 'rotate(-4deg) translateY(20%)', transitionDelay: '0s', }, img2: { left: '50%', top: 0, position: 'absolute', transform: 'translate(-50%, 0)', transitionDelay: '.1s', zIndex: 1, }, img3: { right: '5%', top: 0, position: 'absolute', transform: 'rotate(4deg) translateY(20%)', transitionDelay: '.2s', }, ' &.active': { img1: { transform: 'rotate(-6deg) translateY(50%) scale(1.9)', }, img2: { transform: 'translate(-50%, -2%) scale(1.2)', }, img3: { transform: 'rotate(6deg) translateY(24%) scale(1.2)', }, }, }, body: { fontFamily: 'sans-serif', fontSize: '48px', fontWeight: 'bold', letterSpacing: '1px', margin: 0, }, section: { textAlign: 'center', padding: '500px 0', '&:nth-child(odd)': { background: '#eee', }, }, }; function OverlappingImages() { const wrapper = document.querySelector('.container'); const className = 'active'; const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { wrapper.classList.add(className); return; } wrapper.classList.remove(className); }); }, { threshold: 1, } ); observer.observe(wrapper); return ( <> <section> <p>(scroll down!)</p> </section> <section> <div style={styles.container}> <div style={styles.container.img1}> <img src="https://via.placeholder.com/350x250" alt="img1" /> </div> <div style={styles.container.img2}> <img src="https://via.placeholder.com/350x250" alt="img2" /> </div> <div style={styles.container.img3}> <img src="https://via.placeholder.com/350x250" alt="img3" /> </div> </div> </section> <section> <p>(scroll up!)</p> </section> </> ); } export { OverlappingImages };
问题分析
- DOM操作时机错误:组件渲染时直接调用
document.querySelector('.container'),此时DOM还未挂载,会返回null,导致后续Observer无法正常工作。 - 内联样式不支持CSS嵌套语法:React内联样式是纯对象,不支持
&.active这种CSS类嵌套、伪类/伪元素语法,所以active状态下的样式完全不生效。 - 未清理Observer:组件卸载时没有停止Observer监听,会造成内存泄漏。
- 直接操作DOM类名不符合React范式:应该通过状态管理来控制样式变化,而非直接修改DOM类名。
修正后的代码
import React, { useRef, useEffect, useState } from 'react'; // 基础样式拆分,避免嵌套 const baseContainerStyle: React.CSSProperties = { position: 'relative', height: '400px', margin: '0 50px', }; const baseImageWrapperStyle: React.CSSProperties = { width: '380px', border: '1px solid #000', overflow: 'hidden', lineHeight: 0, transition: 'transform 0.4s ease-in-out', }; const baseImgStyle: React.CSSProperties = { width: '100%', fontSize: 0, }; // 初始状态样式 const img1InitialStyle: React.CSSProperties = { ...baseImageWrapperStyle, left: '5%', top: 0, position: 'absolute', transform: 'rotate(-4deg) translateY(20%)', transitionDelay: '0s', }; const img2InitialStyle: React.CSSProperties = { ...baseImageWrapperStyle, left: '50%', top: 0, position: 'absolute', transform: 'translate(-50%, 0)', transitionDelay: '0.1s', zIndex: 1, }; const img3InitialStyle: React.CSSProperties = { ...baseImageWrapperStyle, right: '5%', top: 0, position: 'absolute', transform: 'rotate(4deg) translateY(20%)', transitionDelay: '0.2s', }; // 激活状态样式 const img1ActiveStyle: React.CSSProperties = { ...img1InitialStyle, transform: 'rotate(-6deg) translateY(50%) scale(1.9)', }; const img2ActiveStyle: React.CSSProperties = { ...img2InitialStyle, transform: 'translate(-50%, -2%) scale(1.2)', }; const img3ActiveStyle: React.CSSProperties = { ...img3InitialStyle, transform: 'rotate(6deg) translateY(24%) scale(1.2)', }; const sectionStyle: React.CSSProperties = { textAlign: 'center', padding: '500px 0', }; const oddSectionStyle: React.CSSProperties = { ...sectionStyle, background: '#eee', }; function OverlappingImages() { const containerRef = useRef<HTMLDivElement>(null); const [isActive, setIsActive] = useState(false); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { // 根据交叉状态更新active状态 setIsActive(entry.isIntersecting); }); }, { threshold: 1, } ); const currentContainer = containerRef.current; if (currentContainer) { observer.observe(currentContainer); } // 组件卸载时清理Observer return () => { if (currentContainer) { observer.unobserve(currentContainer); } }; }, []); return ( <> <section style={oddSectionStyle}> <p>(向下滚动!)</p> </section> <section style={sectionStyle}> <div ref={containerRef} style={baseContainerStyle}> <div style={isActive ? img1ActiveStyle : img1InitialStyle}> <img style={baseImgStyle} src="https://via.placeholder.com/350x250" alt="img1" /> </div> <div style={isActive ? img2ActiveStyle : img2InitialStyle}> <img style={baseImgStyle} src="https://via.placeholder.com/350x250" alt="img2" /> </div> <div style={isActive ? img3ActiveStyle : img3InitialStyle}> <img style={baseImgStyle} src="https://via.placeholder.com/350x250" alt="img3" /> </div> </div> </section> <section style={oddSectionStyle}> <p>(向上滚动!)</p> </section> </> ); } export { OverlappingImages };
关键修改说明
- 使用useRef获取DOM元素:确保组件挂载后能正确获取容器元素,避免
null错误。 - 拆分样式对象:将嵌套的CSS样式拆分为独立的React内联样式对象,通过
isActive状态切换不同样式。 - useEffect管理Observer生命周期:在组件挂载时初始化Observer,卸载时停止监听,避免内存泄漏。
- 状态驱动样式变化:通过
useState维护isActive状态,根据状态切换图片容器的样式,符合React数据驱动视图的范式。
内容的提问来源于stack exchange,提问作者user18943198
相关产品推荐
相关产品推荐

