You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 00:50:16