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

如何避免framer-motion的useInView与useScroll钩子同时触发

Framer Motion 动画未按预期执行

期望动画行为

  • 当section进入视口时,目标元素从垂直方向(y=300)移动到y=50完成入场动画
  • 退场动画随滚动触发,元素向右完全移出视口

实际问题

黑色背景section进入视口时,紫色背景元素直接从屏幕右侧开始向左移动,直到section滚动结束,没有实现先入场再随滚动退场的预期效果。

实现代码

import { motion, useInView, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
import { SectionHeading } from "../UI/Headings";
import { Section } from "../UI/Section";

export default function About() {
  const inViewRef = useRef();
  useInView(inViewRef, { margin: "-180px 0px", once: true });

  const scrollRef = useRef(null);
  const { scrollYProgress } = useScroll({ target: scrollRef});
  const x = useTransform(scrollYProgress, [0, 1], ["0", "120%"]);

  return (
    <Section ref={inViewRef} id="who-i-am" className="bg-black h-[150svh]">
        <motion.div className="h-[150svh]"
        initial={{y: 300, visibility: "hidden"}}
        whileInView={{ y: 50, visibility: "visible" }} 
        // 因y=50,滚动至section末尾时紫色容器会超出50px,故增加section高度
        transition={{ duration: 1.6, ease: "easeIn",  type:"spring" }}>
          <motion.div ref={scrollRef} style={{x}} className="bg-purple-500 sticky top-0">
          <SectionHeading>SECTION HEADING</SectionHeading>
          <p>
            Lorem ipsum dolor, sit amet consectetur adipisicing elit. Temporibus aliquam veniam non quod veritatis sed ex ipsam doloribus. Ratione est quae quo architecto. Labore, quisquam. Ipsam vitae libero veniam eius, pariatur magni perferendis, molestiae numquam, iure dolorum voluptatum nemo? Illum laudantium quae voluptatem reiciendis hic at aut quam ea error.
          </p>
          <p>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Vitae recusandae atque quibusdam ipsa ab fugit minus, veritatis earum molestias repellat.
          </p>
          </motion.div>
        </motion.div>
    </Section>
  );
}

已尝试方案

  • 调整useScroll的offset选项(如["center center"])导致滚动效果消失
  • 尝试通过isInView条件控制水平动画,但未成功

解决方案

问题核心是滚动动画在入场动画完成前就已触发,且滚动目标的范围设置有误。以下是修正后的代码:

import { motion, useInView, useScroll, useTransform } from "framer-motion";
import { useRef, useState } from "react";
import { SectionHeading } from "../UI/Headings";
import { Section } from "../UI/Section";

export default function About() {
  const inViewRef = useRef();
  const [isInView, setIsInView] = useState(false);
  // 监听section是否进入视口
  useInView(inViewRef, { margin: "-180px 0px", once: true }, ({ inView }) => {
    setIsInView(inView);
  });

  const { scrollYProgress } = useScroll({ 
    target: inViewRef, // 滚动目标改为整个section,确保进度基于section的滚动范围
    offset: ["start end", "end start"] // 定义滚动进度区间:section顶部进入视口到底部离开视口
  });

  // 入场完成前保持x=0,完成后再启用滚动位移
  const x = useTransform(
    scrollYProgress, 
    [0, 1], 
    isInView ? ["0", "120%"] : ["0", "0"]
  );

  const [entryComplete, setEntryComplete] = useState(false);

  return (
    <Section ref={inViewRef} id="who-i-am" className="bg-black h-[150svh]">
        <motion.div 
          className="h-[150svh]"
          initial={{y: 300, visibility: "hidden"}}
          whileInView={{ y: 50, visibility: "visible" }} 
          transition={{ duration: 1.6, ease: "easeIn", type:"spring" }}
          onAnimationComplete={() => setEntryComplete(true)} // 监听入场动画完成事件
        >
          <motion.div 
            style={{x}} 
            className="bg-purple-500 sticky top-0"
            // 入场完成前禁用滚动动画过渡
            transition={{ duration: 1, ease: "easeOut", delay: entryComplete ? 0 : Infinity }}
          >
            <SectionHeading>SECTION HEADING</SectionHeading>
            <p>
              Lorem ipsum dolor, sit amet consectetur adipisicing elit. Temporibus aliquam veniam non quod veritatis sed ex ipsam doloribus. Ratione est quae quo architecto. Labore, quisquam. Ipsam vitae libero veniam eius, pariatur magni perferendis, molestiae numquam, iure dolorum voluptatum nemo? Illum laudantium quae voluptatem reiciendis hic at aut quam ea error.
            </p>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipisicing elit. Vitae recusandae atque quibusdam ipsa ab fugit minus, veritatis earum molestias repellat.
            </p>
          </motion.div>
        </motion.div>
    </Section>
  );
}

关键修正点:

  1. 控制滚动动画触发时机:通过useInView的状态和入场动画的onAnimationComplete事件,确保只有入场动画完成后,滚动的水平位移才会生效。
  2. 修正滚动目标范围:将useScroll的target改为整个section的ref,让scrollYProgress基于section从进入到离开视口的完整滚动过程计算。
  3. 动画过渡隔离:给滚动动画设置延迟,入场完成前延迟设为无穷大,避免滚动动画提前触发。

内容的提问来源于stack exchange,提问作者Marya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 09:02:32