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

Framer Motion+Next.js中useScroll绑定元素后失效问题排查

解决Framer Motion useScroll scrollYProgress始终为0的问题

你的scrollYProgress一直为0,导致元素完全不可见,主要是这几个原因,对应解决方法如下:

1. Offset 触发范围设置不合理

你当前的offset: ["start end", "end end"]定义的动画触发区间是:

  • 开始:目标元素顶部和视口底部对齐
  • 结束:目标元素底部和视口底部对齐

这意味着只有当元素从视口底部完全进入,再到完全滚出视口底部的过程中,scrollYProgress才会从0变到1。如果你的元素初始就在视口内,或者滚动范围没覆盖这个区间,scrollYProgress就会一直停在0。

调整方法:把offset改成更贴合需求的范围,比如让元素进入视口80%时开始动画,离开20%时结束:

const { scrollYProgress } = useScroll({
  target: ref,
  offset: ["start 80%", "end 20%"],
});

你也可以根据需求自定义百分比,比如"start 50%"代表元素顶部到达视口中间时触发动画开始。

2. 直接使用scrollYProgress导致初始状态不可见

你直接把scrollYProgress赋值给opacity和scale,当值为0时,元素完全透明且缩放为0,自然看不见。而且从0到1的缩放动画也会很突兀。

解决方法:用useTransform把scrollYProgress的0-1范围映射到合理的动画区间:

// 先导入useTransform
import { motion, useScroll, useTransform } from "framer-motion";

// ...

const { scrollYProgress } = useScroll({
  target: ref,
  offset: ["start 80%", "end 20%"],
});

// 映射透明度:从0.2到1,避免完全不可见
const opacity = useTransform(scrollYProgress, [0, 1], [0.2, 1]);
// 映射缩放:从0.8到1,动画更自然
const scale = useTransform(scrollYProgress, [0, 1], [0.8, 1]);

然后把style里的scrollYProgress换成映射后的变量:

style={{
  opacity,
  scale,
}}

3. 目标元素ref未正确挂载

检查styles.works__wrapper的CSS,确保容器有足够高度,避免元素高度为0导致Framer Motion无法检测位置。可以给容器加个最小高度:

.works__wrapper {
  min-height: 500px; /* 根据你的布局调整数值 */
}

4. 滚动容器不是window的情况

如果SelectedWork嵌套在一个设置了overflow: scroll的容器里,需要在useScroll里指定container参数:

const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
  target: ref,
  container: containerRef,
  offset: ["start 80%", "end 20%"],
});

然后给外层滚动容器绑定ref:

<div ref={containerRef} style={{ overflow: "scroll", height: "100vh" }}>
  <SelectedWork ... />
</div>

完整修改后的代码

"use client";

import { useRef, useMemo } from "react";
import { motion, useScroll, useTransform } from "framer-motion";
import Image from "next/image";
import styles from "./index.module.scss";

const SelectedWork = ({ title, description, year, image }) => {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start 80%", "end 20%"],
  });

  const opacity = useTransform(scrollYProgress, [0, 1], [0.2, 1]);
  const scale = useTransform(scrollYProgress, [0, 1], [0.8, 1]);

  const imageSrc = useMemo(
    () => image.src.split("/")[image.src.split("/").length - 1].split(".")[0],
    [image]
  );

  return (
    <motion.div
      className={styles.works__wrapper}
      id={imageSrc}
      ref={ref}
      style={{
        opacity,
        scale,
      }}
    >
      <div className={styles.image__container__wrapper}>
        <motion.div className={styles.image__container}>
          <div className={styles.image__overlay}>
            <p>See More</p>
          </div>
          <Image
            src={image}
            alt={title}
            sizes="100vw"
            objectFit="contain"
            className={styles.image}
          />
        </motion.div>
      </div>
      <div className={styles.work__details}>
        <div>
          <h5 className={styles.title}>{title}</h5>
        </div>
        <div className={styles.description}>
          <h6>{description}</h6>
        </div>
        <h6>{year}</h6>
      </div>
    </motion.div>
  );
};

export default SelectedWork;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 14:19:56