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. 拖动处理逻辑
分为左侧拖动和右侧拖动两种情况:
左侧拖动要点:
- 拖动时需要同时更新当前元素的位置和宽度
- 向左拖动时,需要考虑左侧元素的最小宽度限制
- 可能会连续影响到多个左侧元素
- 不能超出容器左边界
右侧拖动要点:
- 需要同时更新当前元素和右侧元素的宽度
- 向右拖动时,需要考虑右侧元素的最小宽度限制
- 可能会连续影响到多个右侧元素
- 不能超出容器右边界
效果展示

关键代码实现
主要包含左右拖动的事件处理函数。以下是核心逻辑说明:
- 左侧拖动处理:
- 计算拖动距离
- 更新当前指针位置
- 处理左侧元素的最小宽度限制
- 设置边界条件
- 右侧拖动处理:
- 计算拖动距离
- 更新当前指针位置
- 处理右侧元素的最小宽度限制
- 设置边界条件
代码部分:
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();
}
};总结
这个滑动控件的核心在于:
- 使用指针数组来管理各个元素的边界位置
- 通过拖动事件来更新指针位置
- 考虑最小宽度限制和边界条件
- 处理连续影响的情况
这种实现方式既保证了交互的流畅性,也确保了各个元素的宽度限制。