React实现网格项滚动时收缩与展开动画的方案求助
React实现滚动驱动网格项收缩/展开动画方案
核心实现思路
- 监听页面滚动状态,获取每个网格项与视口的相对位置
- 根据元素进入/离开视口的比例,计算动画进度值
- 基于进度值动态修改网格项的样式属性(高度、缩放、透明度等),实现收缩/展开的过渡效果
推荐工具包
- react-intersection-observer:简化元素视口监听逻辑,无需手动处理滚动事件,支持多阈值触发,适合基础滚动动画场景
- framer-motion:React生态的专业动画库,内置滚动驱动动画、弹簧缓动等特性,能快速实现平滑且精细的过渡效果
- gsap:功能强大的动画引擎,支持滚动触发、时间线控制,适合需要高度自定义的复杂动画需求
具体实现步骤(结合你的代码)
方案1:用react-intersection-observer实现基础监听
- 安装依赖:
npm install react-intersection-observer
- 修改列表渲染代码:
import { useInView } from 'react-intersection-observer'; {projects?.map((projectData) => { const project = tData(projectData); const slug = toKebabCase(`${projectData.en?.client}-${projectData.en?.project}`); // 配置视口监听,设置全范围阈值以获取精确的进入比例 const [ref, , entry] = useInView({ threshold: Array.from({ length: 101 }, (_, i) => i / 100), triggerOnce: false, // 支持滚动来回触发动画 }); // 计算动画进度:0为完全在视口外,1为完全进入视口 const progress = entry?.intersectionRatio || 0; // 根据进度动态设置样式,模拟收缩/展开效果 const itemStyle = { backgroundImage: `url(${project?.mainImage})`, height: `${60 + progress * 40}vh`, // 从60vh展开到100vh transform: `scale(${0.9 + progress * 0.1})`, // 从0.9缩放到1 opacity: 0.7 + progress * 0.3, // 从半透明过渡到完全不透明 transition: 'all 0.3s ease-out', // 平滑过渡 }; return ( <div key={projectData.id} className="projects-grid-item" ref={ref} style={itemStyle} > <div className="project-content-wrapper"> <div className="nox-h-4"> <Link href={`${PROJECTS_ROUTE}/${slug}`}> <a> {project?.client} - {project?.project} </a> </Link> </div> <div className="additional-info-wrapper nox-body-1"> <div className="services">{project?.services}</div> <div>{project?.year}</div> </div> </div> </div> ); })}
方案2:用framer-motion实现滚动驱动动画
- 安装依赖:
npm install framer-motion
- 修改列表渲染代码:
import { motion, useScroll, useTransform } from 'framer-motion'; // 在组件内部初始化滚动监听 const { scrollYProgress } = useScroll(); {projects?.map((projectData, index) => { const project = tData(projectData); const slug = toKebabCase(`${projectData.en?.client}-${projectData.en?.project}`); // 为每个网格项设置专属的滚动触发区间(可根据布局调整参数) const start = index * 0.3; const end = start + 0.3; // 将滚动进度映射为动画属性值 const height = useTransform(scrollYProgress, [start, end], ['60vh', '100vh']); const scale = useTransform(scrollYProgress, [start, end], [0.9, 1]); const opacity = useTransform(scrollYProgress, [start, end], [0.7, 1]); return ( <motion.div key={projectData.id} className="projects-grid-item" style={{ backgroundImage: `url(${project?.mainImage})`, height, scale, opacity, }} > <div className="project-content-wrapper"> <div className="nox-h-4"> <Link href={`${PROJECTS_ROUTE}/${slug}`}> <a> {project?.client} - {project?.project} </a> </Link> </div> <div className="additional-info-wrapper nox-body-1"> <div className="services">{project?.services}</div> <div>{project?.year}</div> </div> </div> </motion.div> ); })}
注意事项
- 先确保基础网格布局(CSS Grid/Flex)已完成,保证每个项的初始空间符合需求
- 动画参数(高度范围、缩放比例、过渡时长)可根据目标网站效果调整
- 若手动实现滚动监听,需添加防抖逻辑避免频繁重渲染,优化性能
内容的提问来源于stack exchange,提问作者Zura Sudadze
相关产品推荐
相关产品推荐

