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
相关产品推荐
相关产品推荐

