如何用Framer Motion实现滚动时的区块动效?
Next.js + Framer Motion 实现滚动动效指南
先确认你已经装了依赖:
npm install framer-motion
核心思路
视频里的动效本质是滚动触发元素入场动画+随滚动进度变化的视差效果,用Framer Motion的几个钩子就能搞定,下面分步骤拆解:
1. 基础入场动画(淡入+位移)
这是视频里最常见的元素从下方/侧边滑入效果,用useInView检测元素是否进入视口,触发动画:
'use client' // Next.js 13+ App Router 必须加,把组件设为客户端组件 import { motion, useInView } from "framer-motion"; import { useRef } from "react"; export default function AnimatedCard() { const cardRef = useRef(null); // once: true 确保动画只触发一次;margin 提前100px就触发动画 const isCardInView = useInView(cardRef, { once: true, margin: "-100px 0px" }); return ( <motion.div ref={cardRef} // 初始状态:透明+向下偏移50px initial={{ opacity: 0, y: 50 }} // 进入视口后恢复正常 animate={isCardInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }} // 动画时长0.6秒,easeOut缓动更自然 transition={{ duration: 0.6, ease: "easeOut" }} className="p-6 bg-white rounded-lg shadow-md" > 你的卡片内容 </motion.div> ); }
2. 滚动视差效果
视频里背景/元素随滚动缓慢移动的效果,用useScroll获取滚动进度,再用useTransform映射成动画属性:
'use client' import { motion, useScroll, useTransform } from "framer-motion"; export default function ParallaxSection() { // 获取页面滚动进度(0到1,从顶部到底部) const { scrollYProgress } = useScroll(); // 滚动时,元素从y=100(向下偏移100px)移动到y=0 const yOffset = useTransform(scrollYProgress, [0, 1], [100, 0]); // 同时从完全透明到不透明 const fadeIn = useTransform(scrollYProgress, [0, 0.2], [0, 1]); return ( <section className="h-screen relative"> {/* 背景层做视差 */} <motion.div style={{ y: yOffset, opacity: fadeIn }} className="absolute inset-0 bg-gradient-to-r from-purple-500 to-blue-500" /> {/* 内容层在上方,不随滚动动 */} <div className="relative z-10 h-screen flex items-center justify-center"> <h1 className="text-5xl text-white font-bold">视差标题</h1> </div> </section> ); }
3. 列表元素依次入场
如果是多个元素要按顺序出现,给每个元素加递增的延迟即可:
'use client' import { motion, useInView } from "framer-motion"; import { useRef } from "react"; export default function AnimatedList() { const listRef = useRef(null); const isListInView = useInView(listRef, { once: true }); const items = ["产品A", "产品B", "产品C", "产品D"]; return ( <div ref={listRef} className="space-y-4"> {items.map((item, idx) => ( <motion.div key={idx} initial={{ opacity: 0, x: -30 }} animate={isListInView ? { opacity: 1, x: 0 } : { opacity: 0, x: -30 }} // 每个元素延迟0.15秒入场,错开时间 transition={{ duration: 0.5, delay: idx * 0.15, ease: "easeOut" }} className="p-4 bg-gray-100 rounded-lg" > {item} </motion.div> ))} </div> ); }
Next.js 专属注意事项
- Next.js 13+ App Router下,所有用到Framer Motion钩子的组件必须加
'use client',否则会出现hydration报错 - 长列表场景下,建议用
React.memo包裹动画组件,避免不必要的重渲染 - 服务端渲染的页面,别在根组件里直接写动画逻辑,拆分到客户端子组件中
调试技巧
- 用Chrome开发者工具的Elements面板,查看
motion元素的style属性,实时调整动画参数 - 修改
useInView的margin值,比如改成"-200px 0px",就能更早触发动画 - 如果动画没触发,先检查
ref有没有正确绑定到元素上,或者视口检测的范围是不是太窄
内容的提问来源于stack exchange,提问作者Filip Kuna
相关产品推荐
相关产品推荐

