Vue 实现可拖拽分割布局(支持左右、上下拖拽调整)

一、前言

在日常的前端开发中,我们经常会遇到这种场景:页面需要分为多个区域,并且用户可以自由调整区域大小
比如常见的后台系统:左边是菜单树,右边是工作区,而右边又需要上下分栏。

如果分栏大小固定,用户体验会很差。理想的交互是:通过拖拽分割线,动态调整布局

本文将带大家用 Vue 实现一个「左右 + 上下」的可拖拽分割布局,并且支持边界限制和窗口自适应。


二、效果图

布局分为三个区域:

| 左侧区域 |    右侧上方区域    |
|          |-------------------|
|          |    右侧下方区域    |

👉 用户可以拖拽中间的分割线,动态改变左右、上下的大小。
在这里插入图片描述


三、核心实现思路

实现可拖拽分割线,一般分为三步:

  1. 开始拖拽:记录鼠标按下时的位置和区域的初始宽高。
  2. 拖拽中:计算偏移量(delta),实时更新区域大小。
  3. 结束拖拽:释放鼠标,取消拖拽状态。

在 Vue 中,可以用 data 保存状态,用 methods 编写事件处理逻辑,并且在 mounted 时监听全局 mousemovemouseup 事件。


四、完整代码示例

1. 模板部分

<template>
  <div id="app">
    <div class="container">
      <!-- 左边区域 -->
      <div class="left" :style="{ width: leftWidth + 'px'}">
      </div>
      <!-- 水平分割线 -->
      <div class="divider-h" @mousedown="startDragH"></div>
      <!-- 右边区域 -->
      <div class="right" :style="{ width: rightWidth + 'px' }">
        <!-- 上部分 -->
        <div class="top" :style="{ height: topHeight + 'px' }"></div>
        <!-- 垂直分割线 -->
        <div class="divider-v" @mousedown="startDragV"></div>
        <!-- 下部分 -->
        <div class="bottom" :style="{ height: bottomHeight + 'px' }"></div>
      </div>
    </div>
  </div>
</template>

2. JS 逻辑部分

export default {
  data() {
    return {
      leftWidth: 400,
      rightWidth: 400,
      topHeight: 300,
      bottomHeight: 300,
      draggingH: false,
      draggingV: false,
      startX: 0,
      startY: 0,
      startLeftWidth: 0,
      startRightWidth: 0,
      startTopHeight: 0,
      startBottomHeight: 0,
    };
  },
  methods: {
    // 开始水平拖拽
    startDragH(e) {
      this.draggingH = true;
      this.startX = e.clientX;
      this.startLeftWidth = this.leftWidth;
      this.startRightWidth = this.rightWidth;
    },
    // 开始垂直拖拽
    startDragV(e) {
      this.draggingV = true;
      this.startY = e.clientY;
      this.startTopHeight = this.topHeight;
      this.startBottomHeight = this.bottomHeight;
    },
    // 拖拽中
    onDrag(e) {
      if (this.draggingH) {
        let delta = e.clientX - this.startX;
        let newLeft = this.startLeftWidth + delta;
        let newRight = this.startRightWidth - delta;

        if (newLeft < 0) {
          newLeft = 0;
          newRight = this.startLeftWidth + this.startRightWidth;
        }
        if (newRight < 0) {
          newRight = 0;
          newLeft = this.startLeftWidth + this.startRightWidth;
        }

        this.leftWidth = newLeft;
        this.rightWidth = newRight;
      }
      if (this.draggingV) {
        let delta = e.clientY - this.startY;
        let newTop = this.startTopHeight + delta;
        let newBottom = this.startBottomHeight - delta;

        if (newTop < 0) {
          newTop = 0;
          newBottom = this.startTopHeight + this.startBottomHeight;
        }
        if (newBottom < 0) {
          newBottom = 0;
          newTop = this.startTopHeight + this.startBottomHeight;
        }

        this.topHeight = newTop;
        this.bottomHeight = newBottom;
      }
    },
    // 结束拖拽
    endDrag() {
      this.draggingH = false;
      this.draggingV = false;
    }
  },
  mounted() {
    document.addEventListener("mousemove", this.onDrag);
    document.addEventListener("mouseup", this.endDrag);
  },
  beforeDestroy() {
    document.removeEventListener("mousemove", this.onDrag);
    document.removeEventListener("mouseup", this.endDrag);
  },
};

3. 样式部分

.container {
  display: flex;
  width: 100%;
  height: 100%;
}

.left {
  background-color: #f5f5f5;
  overflow: auto;
}

.right {
  display: flex;
  flex-direction: column;
}

.divider-h {
  width: 10px;
  cursor: col-resize;
  background-color: #e0e0e0;
}
.divider-h:hover {
  background-color: #c0c0c0;
}

.divider-v {
  height: 10px;
  cursor: row-resize;
  background-color: #e0e0e0;
}
.divider-v:hover {
  background-color: #c0c0c0;
}

五、效果展示

当用户拖拽中间的分割条时,左右、上下区域的大小会随之改变。

此外,通过边界控制,区域不会被拖成负数,保证了页面布局的稳定性。


六、可以优化的点

  1. 节流优化mousemove 可以加上 requestAnimationFramethrottle
  2. 最小/最大限制:防止某区域太小或太大。
  3. 抽象成组件:把分割条封装成 <Divider>,提高复用性。
  4. 持久化保存:通过 localStorage 保存用户上次调整的布局。
  5. 双击收起功能:增强交互体验。

七、总结

本文实现了一个 Vue 可拖拽分割布局,关键点在于:

  • 拖拽三步走mousedown → mousemove → mouseup
  • 状态管理:Vue data 存储宽高。
  • 边界处理:防止负数和溢出。
  • 体验优化:分割线 hover 提示、自适应窗口。

这种布局在后台管理系统、数据大屏、工作台中非常常见,掌握了这套写法后,可以灵活应对各种分栏需求。


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐