如何在Framer Motion的onDrag回调中获取进度条所需的三类宽度属性
解决Framer Motion拖拽轮播的进度条计算问题
你的问题出在Framer Motion的拖拽并非基于原生滚动(scroll)实现,而是通过CSS transform: translateX() 来移动元素,所以你拿不到scrollLeft、scrollWidth这些原生滚动属性。得换个思路,用Framer Motion自身的状态值来追踪拖拽位置,进而计算进度。
下面是具体的修复方案:
步骤1:用useMotionValue追踪拖拽位置
Framer Motion提供了useMotionValue来实时获取元素的拖拽位移,我们可以用它替代原生scroll属性:
import { motion, useMotionValue } from "framer-motion"; // 新增useMotionValue
在组件里创建x轴的运动值:
const x = useMotionValue(0); // 追踪拖拽的x轴位移
把这个值绑定到拖拽元素的x属性上:
<motion.div className="inner-carousel" ref={carouselRef} drag="x" whileTap={{ cursor: "grabbing" }} dragElastic={0.5} dragConstraints={{ right: 0, left: -widthConstraints }} dragListener={true} x={x} // 绑定运动值 >
步骤2:修复useEffect的无限循环问题
你原来的useEffect依赖widthConstraints,会导致每次设置值后触发重新渲染,无限循环。改成依赖carouselRef,只在组件挂载时计算一次最大可拖拽距离:
useEffect(() => { if (carouselRef.current) { setWidthConstraints( carouselRef.current.scrollWidth - carouselRef.current.offsetWidth ); } }, []); // 空依赖,仅挂载时执行
步骤3:实时计算进度值
监听x的位移变化,计算当前拖拽进度(0-100%):
const [progressValue, setProgressValue] = useState(0); useEffect(() => { if (widthConstraints === 0) return; // 监听x值变化,计算进度比例 const unsubscribe = x.onChange((latestX) => { const progress = Math.abs(latestX / widthConstraints) * 100; // 限制进度在0-100之间,避免异常值 setProgressValue(Math.min(Math.max(progress, 0), 100)); }); return unsubscribe; }, [x, widthConstraints]);
步骤4:把进度传给ProgressBar组件
直接将计算好的progressValue传递给进度条组件:
<ProgressBar progress={progressValue} />
完整修改后的代码
import React, { useState, useRef, useEffect } from "react"; import { Box, Flex } from "theme-ui"; import { MainContainer } from "@styles/pages/AboutStyles"; import data from "@public/images.json"; import { motion, useMotionValue } from "framer-motion"; import { ProgressBar } from "@components"; const About: React.FC = () => { const carouselRef = useRef<HTMLDivElement>(null); const x = useMotionValue(0); // 追踪拖拽位移 const [widthConstraints, setWidthConstraints] = useState<number>(0); const [progressValue, setProgressValue] = useState(0); // 计算最大可拖拽距离 useEffect(() => { if (carouselRef.current) { setWidthConstraints( carouselRef.current.scrollWidth - carouselRef.current.offsetWidth ); } }, []); // 实时计算进度值 useEffect(() => { if (widthConstraints === 0) return; const unsubscribe = x.onChange((latestX) => { const progress = Math.abs(latestX / widthConstraints) * 100; setProgressValue(Math.min(Math.max(progress, 0), 100)); }); return unsubscribe; }, [x, widthConstraints]); return ( <Box sx={MainContainer}> <Box sx={{ mt: ["60px", "80px"], width: "100%", height: "100%", position: "relative", }} > <motion.div className="carousel-container"> <motion.div className="inner-carousel" ref={carouselRef} drag="x" whileTap={{ cursor: "grabbing" }} dragElastic={0.5} dragConstraints={{ right: 0, left: -widthConstraints }} dragListener={true} x={x} // 绑定运动值 > {data?.carouselItems.map((n, i) => ( <motion.div className="carousel-items" key={i}> <motion.div className="carousel-img" style={{ backgroundImage: `url(${n.url})`, }} /> </motion.div> ))} </motion.div> </motion.div> <Flex className="w-full justify-end" sx={{ ml: "auto", fontWeight: 400, lineHeight: "11px", fontFamily: "DotGothic16", fontSize: "9px", fontStyle: "normal", mt: "16px", color: "text", }} > horizontal scroll </Flex> <ProgressBar progress={progressValue} /> {/* 传递进度值 */} </Box> </Box> ); }; export default About;
关键说明
- Framer Motion的drag是基于transform实现的,原生scroll属性完全不适用,必须用它提供的运动值API追踪位置
useMotionValue的onChange方法可以实时监听位移变化,适合用来计算进度- 计算进度时要限制值在0-100之间,避免拖拽超出边界时进度条出现异常
内容的提问来源于stack exchange,提问作者user11626619
相关产品推荐
相关产品推荐

