React中如何用鼠标拖动改变灰色区域宽度并联动Range输入框
React 实现拖动灰色区域同步 Range 输入框值
我有一个带 Range 输入框的 React 组件,灰色区域宽度由输入框 value 计算得出,公式如下:
const del = (maxValue - minValue) / 1000; const step = 100 / del; const width = ((value - minValue) / 1000) * step; return width;

需要实现两个核心需求:
- 通过鼠标拖动修改灰色区域宽度
- 同步更新 Range 输入框的 value 值
核心问题:React 中如何实现鼠标拖动改变灰色区域宽度?
以下是我的现有精简代码:
模板代码
<div className="component"> <div className="component-data"> <div className="component-data-active" ref={ref}/> </div> <div className="component__range"> <div className="component__range-wrapper"> <input className="component__range-slider" type="range" max={maxValue} min={minValue} step={step} value={value} onChange={(event) => { setValue(event.target.value) const progress = progressRef.current; if (progress) { const width = computeWidth(event.target.value); progress.style.width = `${width}%`; } }} /> <div ref={progressRef} className="component__range-progress"></div> </div> </div> </div>
JS 逻辑
const ref = useRef(); const progressRef = useRef(null); const [fillPercent, setFillPercent] = useState(50); const changeRangeFill = () => { const active = ref.current; active.style.maxWidth = fillPercent.toString() + '%' } const fillPercentData = () => { const width = computeWidth(value); setFillPercent(width); } useEffect(() => { const progress = progressRef.current; const width = computeWidth(value); progress.style.width = `${width}%`; }, []) useEffect(() => { fillPercentData(); changeRangeFill(); }, [value, fillPercent]) const computeWidth = (value) => { const del = (maxValue - minValue) / 1000; const step = 100 / del; const width = ((value - minValue) / 1000) * step; return width; }
SCSS 样式
.component { position: relative; display: flex; flex-direction: column; z-index: 2; &-data { display: grid; grid-template-columns: repeat(2, 1fr); height: 50px; align-items: center; position: relative; z-index: 0; padding: 0 15px; border: 1px solid #EEF0F4; overflow: hidden; &-active { position: absolute; height: 100%; width: 100%; max-width: 51%; background: #EEF0F4; z-index: 1; cursor: ew-resize; } } &__range { position: absolute; bottom: -6px; width: 100%; &-wrapper{ position: relative; } &-slider { -webkit-appearance: none; width: 104%; height: 0; margin-left: -7px; background: none; outline: none; -webkit-transition: 0.2s; transition: opacity 0.2s; cursor: pointer; } &-slider:hover { opacity: 1; } &-slider::-webkit-slider-thumb { position: relative; -webkit-appearance: none !important; height: 14px; width: 14px; margin-top: -2px; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: red; z-index: 2; } &-progress { position: absolute; content:''; width: 50%; height: 1px; background: red; top: 71%; z-index: 1; } } }
解决方案:实现拖动灰色区域同步 Range 值
1. 核心思路
通过监听鼠标的 mousedown、mousemove、mouseup 事件,计算拖动时的宽度变化,反向推导对应的 Range 值并更新状态,同时同步灰色区域和进度条的宽度。
2. 修改后的完整 JS 逻辑
import { useState, useRef, useEffect } from 'react'; const YourComponent = ({ maxValue, minValue }) => { const ref = useRef(); const progressRef = useRef(null); const [value, setValue] = useState(minValue + (maxValue - minValue) / 2); const [isDragging, setIsDragging] = useState(false); // 计算宽度的方法保持不变 const computeWidth = (val) => { const del = (maxValue - minValue) / 1000; const step = 100 / del; const width = ((val - minValue) / 1000) * step; return Math.max(0, Math.min(100, width)); }; // 反向计算:根据宽度得出对应的value值 const computeValueFromWidth = (widthPercent) => { const del = (maxValue - minValue) / 1000; const step = 100 / del; const val = (widthPercent / step) * 1000 + minValue; return Math.max(minValue, Math.min(maxValue, val)); }; // 更新灰色区域和进度条宽度 const updateWidths = (val) => { const width = computeWidth(val); if (ref.current) { ref.current.style.maxWidth = `${width}%`; } if (progressRef.current) { progressRef.current.style.width = `${width}%`; } }; // 初始化宽度 useEffect(() => { updateWidths(value); }, []); // 当value变化时更新宽度 useEffect(() => { updateWidths(value); }, [value]); // 鼠标按下事件:开始拖动 const handleMouseDown = (e) => { e.preventDefault(); setIsDragging(true); }; // 鼠标移动事件:计算拖动后的宽度并更新value const handleMouseMove = (e) => { if (!isDragging) return; if (!ref.current || !ref.current.parentElement) return; const parentRect = ref.current.parentElement.getBoundingClientRect(); const mouseX = e.clientX - parentRect.left; const widthPercent = (mouseX / parentRect.width) * 100; const newValue = computeValueFromWidth(widthPercent); setValue(newValue); }; // 鼠标松开事件:结束拖动 const handleMouseUp = () => { setIsDragging(false); }; // 添加全局鼠标事件监听 useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div className="component"> <div className="component-data"> <div className="component-data-active" ref={ref} onMouseDown={handleMouseDown} /> </div> <div className="component__range"> <div className="component__range-wrapper"> <input className="component__range-slider" type="range" max={maxValue} min={minValue} step={(maxValue - minValue)/1000} value={value} onChange={(event) => { setValue(Number(event.target.value)); }} /> <div ref={progressRef} className="component__range-progress"></div> </div> </div> </div> ); }; export default YourComponent;
3. 关键修改点说明
- 拖动状态管理:用
isDragging标记是否处于拖动状态,控制鼠标移动事件的触发 - 反向计算逻辑:新增
computeValueFromWidth方法,将宽度百分比转换为对应的 Range 值 - 全局事件监听:拖动时绑定全局
mousemove和mouseup事件,避免鼠标移出组件区域后失效 - 边界限制:对计算出的宽度和 value 做边界处理,确保不会超出 0-100% 和 min-max 范围
- 简化状态依赖:移除多余的
fillPercent状态,直接通过 value 驱动宽度更新,减少状态冗余
内容的提问来源于stack exchange,提问作者uiHellen
相关产品推荐
相关产品推荐

