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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 20:27:02