原生JS实现双栏布局拖拽【附源码】

146 阅读3分钟

大家好,我是前端架构师,关注微信公众号【@程序员大卫】免费领取前端精品资料。

前言

之前有个老的项目,需要使用 jQuery 实现这种双栏布局拖拽的效果,特此记录一下,分享给大家。

实现思路讲解

这个双栏拖拽布局,本质上只做了一件事:

通过拖拽中间的分割线,动态改变左侧容器的宽度,右侧自动填充剩余空间。

我们可以把整个实现拆成 4 个核心步骤 来理解。

一、整体布局结构是关键

先看最核心的 DOM 结构(简化后):

<div class="content-container">
  <div class="left-container"></div>
  <div class="divider-container"></div>
  <div class="right-container"></div>
</div>

为什么要这样结构?

  • 父容器 .content-container 使用 flex
  • 左侧 .left-container 固定宽度(但可变)
  • 中间 .divider-container拖拽手柄
  • 右侧 .right-container 使用 flex-grow: 1 自动填充剩余空间

👉 重点:我们只需要控制 left 的宽度,right 会自动变化

CSS 里最关键的几行是:

.content-container {
  display: flex;
}

.left-container {
  flex-shrink: 0;
}

.right-container {
  flex-grow: 1;
}

二、拖拽的核心原理(一定要理解)

拖拽其实并不神秘,本质只有 3 个事件:

  1. mousedown:开始拖拽
  2. mousemove:拖拽过程中,持续计算距离
  3. mouseup:结束拖拽

拖拽时我们关心什么?

只关心 鼠标在 X 轴上移动了多少

当前宽度 = 初始宽度 + (当前鼠标 X - 按下时鼠标 X)

代码里对应的是这一段:

var startX = e.clientX;       // 鼠标按下时的 X
var leftWidth = $left.width(); // 左侧初始宽度

setLeftContainerWidth(leftWidth + e.clientX - startX);

三、为什么 mousemove 要绑定在 document 上?

这是一个非常重要的细节,新手最容易踩坑。

$document.on("mousemove", onMousemove);
$document.on("mouseup", onMouseup);

如果绑定在 divider 上会怎样?

  • 鼠标一旦移动太快
  • 光标离开 divider 区域
  • 拖拽立刻失效

👉 所以正确做法是绑定到 document,确保鼠标在页面任何地方都能继续拖拽。

四、防止宽度“拖炸”的边界控制

拖拽时一定要做 最大 / 最小宽度限制,否则:

  • 左边会被拖成负数
  • 或者直接盖住右侧

这里用了一个非常关键的方法:

var setLeftContainerWidth = function (width) {
  var contentWidth = $content.width();
  var dividerWidth = $divider.width();
  var maxLeftWidth = contentWidth - dividerWidth;

  width = Math.min(width, maxLeftWidth);
  $left.width(width);
};

这里做了什么?

  • 最大宽度 = 父容器宽度 - 分割线宽度
  • 使用 Math.min 防止超过最大值

👉 这一步是拖拽体验是否“丝滑”的关键点之一

五、为什么要加 body.dragging 这个状态?

当拖拽开始时:

$body.addClass("dragging");

结束时:

$body.removeClass("dragging");

对应 CSS:

body.dragging {
  cursor: col-resize;
}

body.dragging .left-container,
body.dragging .right-container {
  pointer-events: none;
}

这一步解决了什么问题?

  1. 统一鼠标样式
  2. 防止 iframe / 内部元素抢占鼠标事件
  3. 避免拖拽中断(尤其是老项目、复杂页面)

👉 这是很多“看起来能拖,但偶尔会断”的根本解决方案

六、双击 / ESC / 按钮恢复布局的设计思路

为了让体验更好,这里加了几个「快捷恢复」方式:

1️⃣ 点击中间手柄,恢复 50%

recoverWidth();
function recoverWidth() {
  $left.width($content.width() / 2);
}

2️⃣ 按 ESC 键恢复

$document.on("keydown", function (e) {
  if (e.key === "Escape") {
    recoverWidth();
  }
});

3️⃣ 左右双箭头一键全屏

setLeftContainerWidth(0);           // 左边隐藏
setLeftContainerWidth($content.width()); // 左边全屏

并且配合 CSS 动画:

.left-container.animating {
  transition: width 0.4s ease;
}

👉 这让“程序员工具类页面”的体验直接上一个档次

七、实现这个功能的几个核心关键点(必看)

如果你只记住下面 5 条,也能自己写出来 👇

  1. 布局用 flex,只改左侧宽度
  2. mousemove / mouseup 一定绑定 document
  3. 拖拽时要保存起始 X 和初始宽度
  4. 必须做最大 / 最小宽度限制
  5. 拖拽中禁用 pointer-events,防止事件丢失

最后

这个方案虽然用了 jQuery,但实现思路是完全通用的

  • 原生 JS
  • Vue / React
  • 甚至桌面端 WebView

只要你理解了「拖拽本质是计算位移」,这个效果你以后随手就能写出来。

附源码

github.com/zm8/wechat-…