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

实现类似LeetCode的可拖拽分隔边框问题求助

解决LeetCode风格可拖拽分隔条的问题

问题根源分析

你的代码存在两个核心问题:

  • 拖拽延迟:用useState实时更新位置,React状态更新是异步且会触发重渲染,频繁触发就会导致UI跟不上鼠标移动
  • 光标异常:未设置拖拽过程中的光标样式,且事件绑定/移除逻辑存在引用不匹配的问题

改进后的实现代码

import { useEffect, useState, useRef } from 'react';

const SplitPane = () => {
  const [divPosition, setDivPosition] = useState(window.innerWidth - 700);
  const dividerRef = useRef<HTMLDivElement>(null);
  // 保存初始偏移量,避免鼠标位置和分隔条位置错位
  const startOffsetRef = useRef(0);

  useEffect(() => {
    const divider = dividerRef.current;
    if (!divider) return;

    const handleMouseDown = (e: MouseEvent) => {
      // 只允许点击分隔条左侧6px区域触发拖拽
      if ((e.target as HTMLElement).offsetLeft > 6) return;
      
      e.preventDefault();
      // 记录鼠标相对于分隔条左侧的偏移
      startOffsetRef.current = e.clientX - divider.getBoundingClientRect().left;
      // 设置全局光标
      document.body.style.cursor = 'col-resize';
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
    };

    const handleMouseMove = (e: MouseEvent) => {
      // 直接操作DOM更新位置,跳过React状态更新的异步延迟
      const newPosition = e.clientX - startOffsetRef.current;
      // 加边界限制,避免容器被拖到不可用尺寸
      const clampedPosition = Math.max(200, Math.min(window.innerWidth - 200, newPosition));
      divider.style.left = `${clampedPosition}px`;
    };

    const handleMouseUp = () => {
      // 拖拽结束后再更新状态保存位置
      setDivPosition(parseInt(divider.style.left));
      document.body.style.cursor = '';
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };

    divider.addEventListener('mousedown', handleMouseDown);

    return () => {
      divider.removeEventListener('mousedown', handleMouseDown);
    };
  }, []);

  return (
    <div style={{ display: 'flex', height: '100vh', position: 'relative' }}>
      <div style={{ width: `${divPosition}px`, backgroundColor: '#f5f5f5', padding: '16px' }}>
        左侧容器
      </div>
      <div
        ref={dividerRef}
        style={{
          position: 'absolute',
          left: `${divPosition}px`,
          width: '12px',
          height: '100%',
          cursor: 'col-resize',
          backgroundColor: '#e0e0e0',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        {/* 可添加视觉提示 */}
        <div style={{ width: '2px', height: '40px', backgroundColor: '#999' }} />
      </div>
      <div style={{ flex: 1, padding: '16px' }}>
        右侧容器
      </div>
    </div>
  );
};

export default SplitPane;

关键改进点

  • 消除拖拽延迟:拖拽过程中直接操作DOM修改分隔条位置,仅在拖拽结束后更新React状态保存最终位置,彻底规避异步更新的延迟问题
  • 修复光标异常:给分隔条默认设置cursor: col-resize,拖拽时给document.body同步设置相同光标,结束后恢复默认样式
  • 修正事件逻辑:将事件提取为独立函数,保证添加和移除的是同一个引用,避免内存泄漏
  • 位置校正:记录鼠标点击时分隔条的偏移量,防止拖拽时光标与分隔条位置错位
  • 边界限制:添加最小/最大宽度限制,避免容器被拖到不可用尺寸

相关学习资源

  • MDN文档:MouseEvent、Element.getBoundingClientRect()、CSS cursor属性
  • React官方文档:useRef的使用、useEffect的清理机制
  • 前端交互模式:双面板拖拽调整的通用实现思路

内容的提问来源于stack exchange,提问作者Alijonov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 06:45:32