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

