Skip to content

React 可调节宽度的滑块控件实现 ​

需求背景 ​

实现一个可以动态调整子元素宽度的滑块控件。控件具有以下特点:

  • 父容器宽度固定
  • 包含多个子元素
  • 通过拖动分隔条可以自由调整各个子元素的宽度比例
  • 保证每个元素都有最小宽度

核心实现思路 ​

1. 指针数组设计 ​

使用一个指针数组来记录每个滑块的边界位置:

  • 相邻滑块的右指针和左指针位置重叠
  • 两个指针之间的距离即为子元素的宽度
  • 指针位置可直接用于设置元素的 position: left 值
js
const [pointerArr, setPointerArr] = useState([0, 71.2, 142.4, 213.6, 284.8, 356])
const controlerItemRefs = [useRef(null), useRef(null), useRef(null), useRef(null), useRef(null)]

2. 拖动处理逻辑 ​

分为左侧拖动和右侧拖动两种情况:

左侧拖动要点:

  • 拖动时需要同时更新当前元素的位置和宽度
  • 向左拖动时,需要考虑左侧元素的最小宽度限制
  • 可能会连续影响到多个左侧元素
  • 不能超出容器左边界

右侧拖动要点:

  • 需要同时更新当前元素和右侧元素的宽度
  • 向右拖动时,需要考虑右侧元素的最小宽度限制
  • 可能会连续影响到多个右侧元素
  • 不能超出容器右边界

效果展示 ​

滑动控件演示

关键代码实现 ​

主要包含左右拖动的事件处理函数。以下是核心逻辑说明:

  1. 左侧拖动处理:
  • 计算拖动距离
  • 更新当前指针位置
  • 处理左侧元素的最小宽度限制
  • 设置边界条件
  1. 右侧拖动处理:
  • 计算拖动距离
  • 更新当前指针位置
  • 处理右侧元素的最小宽度限制
  • 设置边界条件

代码部分:

js
      const handlerLeftMouseDown = (event) => {
      if (event.button !== 0) return; // 只响应鼠标左键事件
      const minSize = 10;
      const startX = event.clientX;
      const pointerIndex = currentIndex;
      let newPointerArr = pointerArr.slice();
      const copyPointer = pointerArr.slice();
      document.onmousemove = function (e) {
        const endX = e.clientX;
        const diffX = endX - startX;
        /*
        左按钮左移:
            1.改变自身的positon和width
            2.先影响左侧第一个元素的width,当其width达到min时,只影响其position
            3.可能影响左侧全部元素,最多移到左侧边缘
        */
        newPointerArr[pointerIndex] = copyPointer[pointerIndex] + diffX;
        if (diffX < 0) {
          for (let i = pointerIndex - 1; i > 0; i--) {
            if (Math.abs(diffX) + minSize * i > copyPointer[pointerIndex] - copyPointer[i]) {
              newPointerArr[i] = newPointerArr[i + 1] - (pointerIndex - i) * minSize;
              if (newPointerArr[i] < i * minSize) {
                newPointerArr[i] = i * minSize
              }
            } else {
              break;
            }
          }
        }
        //左移边界条件
        if (newPointerArr[pointerIndex] < pointerIndex * minSize) {
          newPointerArr[pointerIndex] = pointerIndex * minSize
        }
        //右移边界条件
        if (copyPointer[pointerIndex + 1] - newPointerArr[pointerIndex] < minSize) {
          newPointerArr[pointerIndex] = copyPointer[pointerIndex + 1] - minSize
        }
        setPointerArr(newPointerArr.slice());
      };
      document.onmouseup = function () {
        document.onmousemove = null;
        document.onmouseup = null;
      };
      if (event.preventDefault) {
        event.preventDefault();
      }
    };

    const handlerRightMouseDown = (event) => {
      if (event.button !== 0) return; // 只响应鼠标左键事件
      const startX = event.clientX;
      const minSize = 10;
      const pointerIndex = currentIndex + 1;
      let newPointerArr = pointerArr.slice();
      const copyPointer = pointerArr.slice();
      const tailLength = (pointerArr.length - 1 - pointerIndex)
      //记录后续元素的原始宽度
      document.onmousemove = function (e) {
        const endX = e.clientX;
        const diffX = endX - startX;
        newPointerArr[pointerIndex] = copyPointer[pointerIndex] + diffX;
        if (diffX > 0) {
          for (let i = pointerIndex + 1; i < pointerArr.length - 1; i++) {
            if (diffX + copyPointer[pointerIndex] + minSize > copyPointer[i]) {
              newPointerArr[i] = newPointerArr[pointerIndex] + (i - pointerIndex) * minSize
              if (newPointerArr[i] > 363 - (tailLength - i + pointerIndex) * minSize) {
                newPointerArr[i] = 363 - (tailLength - i + pointerIndex) * minSize
              }
            } else {
              break;
            }
          }
        }
        if (newPointerArr[pointerIndex] > 363 - tailLength * minSize) {
          newPointerArr[pointerIndex] = 363 - tailLength * minSize
        }

        if (newPointerArr[pointerIndex] - copyPointer[pointerIndex - 1] < minSize) {
          newPointerArr[pointerIndex] = copyPointer[pointerIndex - 1] + minSize
        }

        setPointerArr(newPointerArr.slice());
      };
      document.onmouseup = function () {
        document.onmousemove = null;
        document.onmouseup = null;
      };
      if (event.preventDefault) {
        event.preventDefault();
      }
    };

总结 ​

这个滑动控件的核心在于:

  1. 使用指针数组来管理各个元素的边界位置
  2. 通过拖动事件来更新指针位置
  3. 考虑最小宽度限制和边界条件
  4. 处理连续影响的情况

这种实现方式既保证了交互的流畅性,也确保了各个元素的宽度限制。