拖动边栏时应避免直接修改 flex-basis 以防卡顿和失效,正确做法是用变量控制宽度、requestAnimationFrame 节流更新、全局监听 mouseup/mouseleave 防锁死、手动设置 resizer 的 cursor 和 user-select,并为移动端适配 touch 事件。

拖动时直接改 flex-basis 会卡顿甚至失效
因为 flex-basis 是布局属性,每次修改都会触发重排(reflow),连续拖动时浏览器来不及响应,视觉上卡顿、跳变,甚至出现“松手后回弹”或“拖不动”的假死现象。真实项目里别用 style.flexBasis = '200px' 配合 mousemove。
正确做法是把边栏宽度转为独立的、不影响布局计算的变量控制:
- 用一个
width变量(如sidebarWidth)存当前像素值 - 在拖动中只更新该变量,不碰 DOM
- 用
requestAnimationFrame节流 + 单次写入样式,比如:el.style.flexBasis = sidebarWidth + 'px';
- 初始值建议设为
min(300px, 30%)这类相对值,避免硬编码导致响应式断裂
鼠标拖拽逻辑必须处理 mouseleave 和 mouseup 失控问题
用户拖到边栏外松手,mouseup 就不会触发,导致边栏“锁死”在拖拽状态。很多实现漏掉这个边界场景。
安全写法是监听全局事件,且绑定/解绑要严格配对:
立即学习“前端免费学习笔记(深入)”;
- 在
mousedown时,立刻调用document.addEventListener('mousemove', onDrag)和document.addEventListener('mouseup', cleanup) - 同时加
document.addEventListener('mouseleave', cleanup),防止鼠标划出窗口 -
cleanup函数里必须移除全部三个监听器,并重置isDragging状态位 - 别用
onDrag闭包捕获旧的el引用——DOM 重绘后可能已不是同一个元素
分隔线(resizer)的 cursor 和 user-select 必须手动干预
默认情况下,拖拽分隔线时文字被选中、光标仍是 text 或 default,体验极差。
两行 CSS 就能解决:
.resizer {
cursor: ew-resize;
user-select: none;
}
注意:如果分隔线是伪元素(::before),cursor 必须加在父容器上;若用了 pointer-events: none,记得给伪元素单独设 pointer-events: auto,否则拖不动。
移动端需要额外支持 touchstart/touchmove,但不能简单复制鼠标逻辑
触摸事件坐标是 touches[0].clientX,不是 clientX;而且一次触摸可能带多个触点,不判空会报错。
兼容写法要点:
- 用统一函数取坐标:
function getClientX(e) { return e.touches ? e.touches[0].clientX : e.clientX; } -
touchstart里调用e.preventDefault(),否则 iOS 会触发页面滚动 - 不要同时监听
mousedown和touchstart—— 在支持触摸的桌面设备(如 Surface)上会触发两次 - 检测是否为触摸设备,推荐用
'ontouchstart' in window,比 UA 判断更可靠









