Framer Motion水平轮播视差效果失效问题排查
问题:Framer Motion水平滚动轮播中图片视差效果不生效
我尝试使用Framer Motion的useScroll和useTransform钩子实现带有图片视差效果的水平滚动轮播,但视差效果未生效——滚动时图片的X位置无明显变化。父组件的水平滚动功能正常,但ParallaxHoriz组件内的scrollYProgress值始终保持恒定,视差变换未按预期触发。
父组件代码(HorizontalScrollCarousel)
import { motion, useTransform, useScroll } from "framer-motion"; import { useRef } from "react"; import ParallaxHoriz from '../../animations/ParallaxHoriz/ParallaxHoriz'; import "./HorizontalScrolling.css"; const HorizontalScrollCarousel = () => { const targetRef = useRef(null); const { scrollYProgress } = useScroll({ target: targetRef, }); const x = useTransform(scrollYProgress, [0, 1], ["0%", "-100%"]); return ( <section ref={targetRef} className="horizontal-scroll-carousel"> <div className="carousel-container"> <motion.div style={{ x }} className="carousel-content"> <div className="carousel-card-container"> {cards.map((card) => ( <div key={card.id} className="card"> <div className="card-image-container"> <ParallaxHoriz> <div className="card-image" style={{ backgroundImage: `url(${card.url})`, width: "100%", height: "100%", }} ></div> </ParallaxHoriz> </div> </div> ))} </div> </motion.div> </div> </section> ); }; export default HorizontalScrollCarousel; const cards = [ { url: "image1.jpg", id: 1 }, { url: "image2.jpg", id: 2 }, // Additional cards... ];
视差组件代码(ParallaxHoriz)
import React from 'react'; import { motion, useScroll, useTransform } from 'framer-motion'; const ParallaxHoriz = ({ children }) => { const ref = React.useRef(null); const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"], }); const x = useTransform(scrollYProgress, [0, 1], ["-20%", "20%"]); return ( <div ref={ref} style={{ overflow: "hidden", width: "100%", height: "100%" }}> <motion.div style={{ width: "120%", height: "100%", x, position: "absolute", left: "-10%", }} > {children} </motion.div> </div> ); }; export default ParallaxHoriz;
CSS样式
.horizontal-scroll-carousel { position: relative; height: 1000vh; background-color: #fdf7f0; } .carousel-container { position: sticky; top: 0; height: 100vh; display: flex; overflow: hidden; } .carousel-content { display: flex; will-change: transform; } .carousel-card-container { display: flex; gap: 16px; } .card { display: flex; height: 100vh; width: 100vw; } .card-image { position: absolute; inset: 0; background-size: cover; background-position: center; }
问题根源
- 滚动上下文错误:
ParallaxHoriz组件监听自身元素的滚动,但实际页面滚动是由父组件horizontal-scroll-carousel(1000vh高度)触发的,子组件自身没有滚动空间,导致scrollYProgress始终不变。 - 目标元素无滚动范围:
ParallaxHoriz的ref绑定在高度100%的容器上,其父元素是sticky定位的carousel-container(高度100vh),该容器不会随页面滚动,因此子组件无法捕获滚动进度变化。
解决方案
步骤1:传递父组件的滚动进度给子组件
将父组件中获取的scrollYProgress作为props传递给ParallaxHoriz,让子组件使用正确的全局滚动上下文:
// 修改HorizontalScrollCarousel中的卡片渲染部分 {cards.map((card) => ( <div key={card.id} className="card"> <div className="card-image-container"> {/* 传递父组件的scrollYProgress */} <ParallaxHoriz scrollYProgress={scrollYProgress}> <div className="card-image" style={{ backgroundImage: `url(${card.url})`, width: "100%", height: "100%", }} ></div> </ParallaxHoriz> </div> </div> ))}
步骤2:修改子组件使用传入的滚动进度
移除子组件自身的useScroll钩子,直接使用父组件传递的滚动进度计算视差变换:
// 修改ParallaxHoriz组件 import React from 'react'; import { motion, useTransform } from 'framer-motion'; const ParallaxHoriz = ({ children, scrollYProgress }) => { // 使用父组件传递的scrollYProgress计算视差 const x = useTransform(scrollYProgress, [0, 1], ["-20%", "20%"]); return ( <div style={{ overflow: "hidden", width: "100%", height: "100%" }}> <motion.div style={{ width: "120%", height: "100%", x, position: "absolute", left: "-10%", }} > {children} </motion.div> </div> ); }; export default ParallaxHoriz;
可选优化:限制视差触发范围
如果希望每张卡片的视差仅在进入/离开视口时生效,可以结合useInView钩子控制:
// 优化后的ParallaxHoriz组件 import React from 'react'; import { motion, useTransform, useInView } from 'framer-motion'; const ParallaxHoriz = ({ children, scrollYProgress }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once: false, margin: "-100px" }); const x = useTransform( scrollYProgress, [0, 1], ["-20%", "20%"], { clamp: false, ease: "linear" } ); return ( <div ref={ref} style={{ overflow: "hidden", width: "100%", height: "100%" }}> <motion.div style={{ width: "120%", height: "100%", x: isInView ? x : 0, position: "absolute", left: "-10%", }} > {children} </motion.div> </div> ); }; export default ParallaxHoriz;
内容的提问来源于stack exchange,提问作者Sithil Sandinu
相关产品推荐
相关产品推荐

