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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:10:02