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

ReactJS实现带拖拽与按钮控制的Framer Motion滑块

问题描述

我是一名新开发者,已实现Framer Motion滑块的拖拽功能,现在希望实现点击按钮时控制translateX偏移,每次点击移动固定像素比例,同时不破坏拖拽的约束,滑块到达两端时对应按钮禁用。现有代码逻辑混乱,请求指导实现该功能。

滑块效果示意图:
滑块示意图

解决方案

核心思路是用Framer Motion的useMotionValue统一管理滑块的X偏移量,将拖拽和按钮点击的控制逻辑收敛,同时实时计算偏移量判断按钮禁用状态。以下是重构后的完整代码:

import { useState, useEffect, useRef } from "react";
import { motion, useMotionValue } from "framer-motion";
import { IoIosArrowDroprightCircle, IoIosArrowDropleftCircle } from "react-icons/io";
import "./skillsSlider.css";

// 图片导入
import javaScriptLogo from "../../assets/images/Javascript-shield.png";
import HTMLAndCSSLogo from "../../assets/images/pngfind.com-HTML-CSS.png";
import nodeLogo from "../../assets/images/Node.js_logo.svg.png";
import expressLogo from "../../assets/images/express-logo.png";
import ReactLogo from "../../assets/images/pngwing.com-react-logo.png";
import reduxLogo from "../../assets/images/pngwing.com-Redux.png";
import postgresLogo from "../../assets/images/pngwing.com-PostgreSql.png";
import mySqlLogo from "../../assets/images/pngwing.com-MySQL.png";
import awsLogo from "../../assets/images/aws.png";
import javaLogo from "../../assets/images/pngwing.com-java.png";
import mongoDbLogo from "../../assets/images/pngwing.com-mongodb.png";
import gitLogo from "../../assets/images/pngegg-git.png";
import gitHubLogo from "../../assets/images/Github_logo.png";
import dockerLogo from "../../assets/images/pngwing.com-Docker.png";

const SkillsSlider = () => {
  const carouselRef = useRef(null);
  // 每次点击移动的固定步长(可根据需求调整)
  const STEP = 300;
  // 用motionValue统一管理滑块X轴偏移量
  const x = useMotionValue(0);
  // 滑块最大可左移距离 = 总宽度 - 容器可视宽度
  const [maxLeftOffset, setMaxLeftOffset] = useState(0);
  // 按钮禁用状态
  const [disableLeft, setDisableLeft] = useState(true);
  const [disableRight, setDisableRight] = useState(false);

  // 初始化计算最大偏移量
  useEffect(() => {
    if (!carouselRef.current) return;
    const totalWidth = carouselRef.current.scrollWidth;
    const viewportWidth = carouselRef.current.offsetWidth;
    setMaxLeftOffset(totalWidth - viewportWidth);
  }, []);

  // 监听偏移量变化,实时更新按钮禁用状态
  useEffect(() => {
    const unsubscribe = x.onChange((currentX) => {
      // currentX为负数时表示滑块左移,绝对值越大越靠左
      setDisableLeft(currentX >= 0);
      setDisableRight(Math.abs(currentX) >= maxLeftOffset);
    });
    return unsubscribe;
  }, [x, maxLeftOffset]);

  // 左按钮逻辑:向右移动(偏移量增大)
  const handleLeftClick = () => {
    const currentX = x.get();
    // 不超过右边界(0)
    const newX = Math.min(currentX + STEP, 0);
    x.set(newX);
  };

  // 右按钮逻辑:向左移动(偏移量减小)
  const handleRightClick = () => {
    const currentX = x.get();
    // 不超过左边界(-maxLeftOffset)
    const newX = Math.max(currentX - STEP, -maxLeftOffset);
    x.set(newX);
  };

  return (
    <>
      <div className="btnsContainer">
        <button
          disabled={disableLeft}
          className="actionLeftContainer"
          onClick={handleLeftClick}
        >
          <IoIosArrowDropleftCircle />
        </button>
        <button
          disabled={disableRight}
          className="actionRightContainer"
          onClick={handleRightClick}
        >
          <IoIosArrowDroprightCircle />
        </button>
      </div>
      <div className="sliderContainer">
        <motion.div
          style={{ x }}
          drag="x"
          dragConstraints={{ right: 0, left: -maxLeftOffset }}
          className="slider"
          ref={carouselRef}
        >
          {/* 滑块内容 */}
          <div>
            <img loading="lazy" src={javaScriptLogo} alt="Javascript" />
          </div>
          <div>
            <img loading="lazy" src={HTMLAndCSSLogo} alt="HTML & CSS" />
          </div>
          <div>
            <img loading="lazy" src={nodeLogo} alt="Node.js" />
          </div>
          <div>
            <img loading="lazy" src={expressLogo} alt="Express" />
          </div>
          <div>
            <img loading="lazy" src={ReactLogo} alt="React" />
          </div>
          <div>
            <img loading="lazy" src={reduxLogo} alt="Redux" />
          </div>
          <div>
            <img loading="lazy" src={postgresLogo} alt="PostgreSQL" />
          </div>
          <div>
            <img loading="lazy" src={mySqlLogo} alt="MySQL" />
          </div>
          <div>
            <img loading="lazy" src={awsLogo} alt="AWS" />
          </div>
          <div>
            <img loading="lazy" src={javaLogo} alt="Java" />
          </div>
          <div>
            <img loading="lazy" src={mongoDbLogo} alt="MongoDB" />
          </div>
          <div>
            <img loading="lazy" src={gitLogo} alt="Git" />
          </div>
          <div>
            <img loading="lazy" src={gitHubLogo} alt="GitHub" />
          </div>
          <div>
            <img loading="lazy" src={dockerLogo} alt="Docker" />
          </div>
        </motion.div>
      </div>
    </>
  );
};

export default SkillsSlider;

关键改进说明

  • 统一状态管理:用useMotionValue替代原有的多个零散状态变量,同时控制拖拽和按钮点击的偏移,避免逻辑冲突。
  • 固定步长控制:定义STEP常量,每次点击按钮移动固定像素,可根据布局需求灵活调整。
  • 实时边界判断:监听偏移量变化,自动计算是否到达滑块两端,无需手动解析样式字符串判断位置。
  • 约束保持一致:拖拽的dragConstraints和按钮点击的边界判断使用同一个maxLeftOffset值,确保两种操作的约束逻辑完全统一。
  • 精简冗余代码:移除了原有的AnimatePresence、variants等不必要的动画逻辑,保留核心交互,代码更简洁易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:20:31