Element Plus Splitter 分割面板组件完整指南:拖拽布局、折叠与懒加载模式深度解析
Splitter 是 Element Plus 提供的一款布局分割组件(beta 状态),它可以将容器区域按水平或垂直方向划分为多个面板,并通过拖拽分隔条自由调整各面板大小,同时支持面板折叠、最小/最大尺寸约束与懒更新等能力。读完本文,你将掌握 el-splitter 与 el-splitter-panel 的全部配置项、事件用法、插槽与暴露方法,并能结合源码理解其尺寸分配、拖拽边界与折叠恢复的底层实现。
Splitter 组件概览
Splitter 的核心设计是一对一的父子结构:ElSplitter 作为容器负责整体布局与拖拽状态管理,ElSplitterPanel 作为面板承载具体内容。在 packages/components/splitter/index.ts 中可以看到,ElSplitter 通过 withInstall(Splitter, { SplitPanel }) 注册,因此既可以使用 <el-splitter><el-splitter-panel /></el-splitter> 的组合写法,也可以单独引入 ElSplitter 与 ElSplitterPanel;两个组件均已接入全局组件注册表(见 packages/element-plus/component.ts 中的 ElSplitter, ElSplitterPanel 导出)。
从源码结构看,splitter 模块由容器组件 splitter.vue、面板组件 split-panel.vue、分隔条组件 split-bar.vue 以及四个逻辑 hooks(useContainer、usePanel、useResize、useSize)组成,职责划分清晰:容器管状态、面板管尺寸、分隔条管交互。
基础用法:自动均分与手动指定尺寸
未传入任何默认尺寸时,Splitter 会依据面板数量自动均分空间。以下是最基础的用法:
<template>
<div style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px">
<el-splitter>
<el-splitter-panel size="30%">
<div class="demo-panel">1</div>
</el-splitter-panel>
<el-splitter-panel :min="200">
<div class="demo-panel">2</div>
</el-splitter-panel>
</el-splitter>
</div>
</template>
<style scoped>
.demo-panel {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
</style>
(完整示例见 docs/examples/splitter/basic.vue)
示例中第一个面板通过 size="30%" 指定了初始宽度,第二个面板通过 :min="200" 约束最小宽度为 200px。需要注意:Splitter 需要一个确定尺寸的父容器,示例中通过外层 div 的 height: 250px 提供高度约束,这也是实际项目中必须满足的前提。
关于未指定尺寸时的分配逻辑,可以看 useSize.ts 的实现:组件会把每个面板的 size 属性统一换算成百分比——30% 这类字符串百分比直接取数值,200px 这类像素值按 px / containerSize 换算,纯数字则视为像素值除以容器尺寸;随后将所有百分比求和,若总和小于 1 则把剩余空间平均分给未指定尺寸的面板(avgRest = (1 - totalPtg) / emptyCount),若总和大于 1 则整体等比缩放(scale = 1 / totalPtg)。这一逻辑保证了无论面板数量与初始配置如何,最终尺寸总和始终等于容器大小。
垂直布局
通过 layout="vertical" 可以将分割方向切换为垂直,分隔条变为上下拖拽,光标样式也会相应变为 ns-resize(见 split-bar.vue 中 draggerStyles 的计算逻辑):
<template>
<div style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px">
<el-splitter layout="vertical">
<el-splitter-panel>
<div class="demo-panel">1</div>
</el-splitter-panel>
<el-splitter-panel>
<div class="demo-panel">2</div>
</el-splitter-panel>
</el-splitter>
</div>
</template>
(完整示例见 docs/examples/splitter/vertical.vue)
水平布局下拖拽偏移取 pageX 差值,垂直布局下取 pageY 差值(见 split-bar.vue 的 onMouseMove)。Splitter 支持任意层级嵌套,例如在某个水平面板内部再嵌一个 layout="vertical" 的 Splitter,即可构造复杂的"田字格"工作区。
面板折叠(Collapsible)
配置 collapsible 后,分隔条两侧会出现折叠箭头按钮,点击即可将相邻面板快速收缩到 0。折叠能力针对每个面板独立配置,collapsible 同时作用于该面板两侧的分隔条。
官方折叠示例(见 docs/examples/splitter/collapsible.vue)演示了面板 1、2、4、5 均开启折叠、面板 3 嵌套垂直 Splitter 的复杂场景:
<script setup lang="ts">
import { ref } from 'vue'
const isCollapsible = ref(true)
</script>
<template>
<el-switch
v-model="isCollapsible"
active-text="enable"
inactive-text="disable"
inline-prompt
class="mb-2"
/>
<div style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px">
<el-splitter>
<el-splitter-panel :collapsible="isCollapsible" min="50">
<div class="demo-panel">1</div>
</el-splitter-panel>
<el-splitter-panel :collapsible="isCollapsible">
<div class="demo-panel">2</div>
</el-splitter-panel>
<el-splitter-panel>
<div class="demo-panel">3</div>
</el-splitter-panel>
<el-splitter-panel :collapsible="isCollapsible">
<el-splitter layout="vertical">
<el-splitter-panel :collapsible="isCollapsible">
<div class="demo-panel">4</div>
</el-splitter-panel>
<el-splitter-panel :collapsible="isCollapsible">
<div class="demo-panel">5</div>
</el-splitter-panel>
</el-splitter>
</el-splitter-panel>
</el-splitter>
</div>
</template>
源码层面,usePanel.ts 中的 getCollapsible 把布尔值展开为 { start: boolean, end: boolean } 两个方向的折叠开关,isCollapsible 则判断某条分隔条两侧是否具备可折叠条件:当前面板的 end 侧可折叠且尺寸大于 0,或下一个面板的 start 侧可折叠且当前面板有尺寸,两种情况都会显示折叠按钮。折叠切换的具体尺寸转移逻辑在 useResize.ts 的 onCollapse 中实现:折叠时把当前面板尺寸转移给相邻面板并缓存原尺寸(cacheCollapsedSize),再次点击时按缓存值还原,并通过 clamp 保证还原尺寸不会超出两者总尺寸范围。
折叠按钮的默认图标来自 @element-plus/icons-vue:水平布局为左右箭头,垂直布局为上下箭头(见 split-bar.vue)。需要自定义按钮外观时,可使用面板暴露的 start-collapsible 与 end-collapsible 插槽。
禁用拖拽(resizable)
当任一面板设置 resizable=false 时,与之相邻的分隔条拖拽即被禁用——分隔条是否可拖取决于其左右(或上下)两个面板是否都允许调整。官方示例(见 docs/examples/splitter/disableDrag.vue)用开关控制中间面板的 resizable:
<script setup lang="ts">
import { ref } from 'vue'
const resizable = ref(false)
</script>
<template>
<el-switch
v-model="resizable"
active-text="enable"
inactive-text="disable"
inline-prompt
class="mb-2"
/>
<div style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px">
<el-splitter>
<el-splitter-panel>
<div class="demo-panel">1</div>
</el-splitter-panel>
<el-splitter-panel :resizable="resizable">
<div class="demo-panel">drag {{ resizable ? 'enable' : 'disable' }}</div>
</el-splitter-panel>
<el-splitter-panel>
<div class="demo-panel">3</div>
</el-splitter-panel>
</el-splitter>
</div>
</template>
在 split-bar.vue 的 onMousedown 中,if (!props.resizable) return 直接拦截了拖拽起点事件;同时光标样式变为 auto,分隔条类名会附加 is-disabled。值得注意的是,禁用拖拽并不影响折叠按钮,折叠仍可通过点击箭头触发。
面板尺寸:v-model:size 双向绑定
通过 v-model:size 可以读取并控制面板尺寸,尺寸支持像素与百分比两种单位。官方示例(见 docs/examples/splitter/size.vue)将第二个面板的实时尺寸渲染到面板内容中,并同时监听三个拖拽事件:
<script setup lang="ts">
import { ref } from 'vue'
const size = ref(100)
const handleResizeStart = (index: number, sizes: number[]) => {
console.log('resizeStart', index, sizes)
}
const handleResize = (index: number, sizes: number[]) => {
console.log('resize', index, sizes)
}
const handleResizeEnd = (index: number, sizes: number[]) => {
console.log('resizeEnd', index, sizes)
}
</script>
<template>
<div style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px">
<el-splitter
@resize-start="handleResizeStart"
@resize-end="handleResizeEnd"
@resize="handleResize"
>
<el-splitter-panel>
<div class="demo-panel">1</div>
</el-splitter-panel>
<el-splitter-panel v-model:size="size" :max="200" :min="50">
<div class="demo-panel">{{ size }}px</div>
</el-splitter-panel>
<el-splitter-panel>
<div class="demo-panel">3</div>
</el-splitter-panel>
</el-splitter>
</div>
</template>
该示例同时展示了 min 与 max 的约束效果:面板初始 100px,拖拽时被限制在 50px 到 200px 之间。从 split-panel.ts 的定义看,size、min、max 的类型均为 string | number,即 50 或 "50px" 等价,"50%" 则以容器尺寸百分比生效。
尺寸约束在 useResize.ts 中落实:拖拽计算时依次校验起始面板与结束面板的 min/max(getLimitSize 会把百分比换算成像素),任一方向越界都会把实际偏移量 mergedOffset 钳制到合法区间,保证面板永远不会被拖出边界。
懒加载模式(Lazy)
lazy 模式适用于面板内容较重、拖拽过程中不希望频繁重排的场景。开启后,拖拽时分隔条会跟随鼠标移动,但各面板的实际尺寸(以及 v-model:size、resize 事件)只在拖拽结束时一次性更新。示例(见 docs/examples/splitter/lazy.vue)为三个面板同时开启折叠与懒模式:
<template>
<div style="height: 250px; box-shadow: var(--el-border-color-light) 0px 0px 10px">
<el-splitter lazy>
<el-splitter-panel collapsible min="50">
<div class="demo-panel">1</div>
</el-splitter-panel>
<el-splitter-panel collapsible>
<div class="demo-panel">2</div>
</el-splitter-panel>
<el-splitter-panel collapsible>
<div class="demo-panel">3</div>
</el-splitter-panel>
</el-splitter>
</div>
</template>
其底层实现在 useResize.ts:拖拽过程中 onMoving 只把待提交的尺寸暂存进 updatePanelSizes 闭包,并借助 lazyOffset 让分隔条在视觉上先行移动(容器样式 --el-splitter-bar-offset 由 splitter.vue 计算);onMoveEnd 时才真正执行 updatePanelSizes() 提交尺寸。同时 splitter.vue 在懒模式下不会触发 resize 事件,只在结束后触发 resizeEnd。
另外,源码对"拖拽中途切换 lazy 开关"做了兜底:useResize 中 watch(lazy) 检测到开启状态变化时会主动派发一次 mouseup 事件结束当前拖拽(useResize.ts),避免状态悬空。
Splitter API 详解
Splitter Attributes
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| layout | 分割方向 | ^[enum]'horizontal' | 'vertical' |
horizontal |
| lazy ^(2.11.0) | 是否启用懒加载模式 | ^[boolean] | false |
属性定义见 splitter.ts,layout 通过 values 白名单约束取值,lazy 为纯布尔开关。
Splitter Events
| 名称 | 说明 | 类型 |
|---|---|---|
| resize-start | 开始拖拽某条分隔条时触发,index 为分隔条索引 |
^[Function](index: number, sizes: number[]) => void |
| resize | 拖拽过程中触发,index 为分隔条索引 |
^[Function](index: number, sizes: number[]) => void |
| resize-end | 拖拽结束时触发,index 为分隔条索引 |
^[Function](index: number, sizes: number[]) => void |
| collapse ^(2.10.3) | 面板被折叠或展开时触发,index 为分隔条索引,type 表示折叠方向 |
^[Function](index: number, type: 'start' | 'end', sizes: number[]) => void |
事件回调的第二个参数 sizes 是当前所有面板的像素尺寸数组,可用于持久化布局状态。事件在 splitter.ts 中声明,并由 splitter.vue 中 onResizeStart、onResize、onResizeEnd、onCollapsible 四个包装函数统一发出;其中 collapse 事件在拖拽结束、尺寸真正落定(nextTick 之后)才触发。
SplitterPanel API 详解
SplitterPanel Attributes
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| size / v-model:size | 面板尺寸(像素或百分比) | ^[string] / ^[number] | - |
| min | 面板最小尺寸(像素或百分比) | ^[string] / ^[number] | - |
| max | 面板最大尺寸(像素或百分比) | ^[string] / ^[number] | - |
| resizable | 面板是否可被拖拽调整 | ^[boolean] | true |
| collapsible | 面板是否可折叠 | ^[boolean] | false |
属性默认值可对照 split-panel.ts:resizable 默认 true,collapsible 默认 false,size/min/max 不设默认值(缺省时按均分规则分配)。collapsible 在 usePanel.ts 中还会被展开为 { start, end } 两个方向分别判断。
SplitterPanel Events
| 名称 | 说明 | 类型 |
|---|---|---|
| update:size | 面板尺寸变化时触发 | ^[Function](size: number) => void |
update:size 与 v-model:size 配套使用,定义于 split-panel.ts。
SplitterPanel Slots
| 名称 | 说明 |
|---|---|
| default | 面板默认内容 |
| start-collapsible | 起始侧折叠按钮的自定义内容 |
| end-collapsible | 结束侧折叠按钮的自定义内容 |
两个折叠插槽在 split-bar.vue 的模板中渲染,若不提供则使用内置的左右/上下箭头图标。
SplitterPanel Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| splitterPanelRef ^(2.11.9) | SplitterPanel 的 DOM 元素 | ^[object]Ref<HTMLDivElement> |
splitterPanelRef 自 2.11.9 起可用,用于在需要直接操作面板 DOM(如测量高度、绑定第三方库)的场景下获取元素引用。
源码级原理:拖拽与折叠的工作机制
尺寸计算管线
整个组件的尺寸状态流可概括为:面板通过 provide/inject 向容器注册(registerPanel,见 splitter.vue 与 split-panel.vue 的上下文注入校验,面板脱离容器使用时会被 throwError 提示正确用法)→ useSize 将各面板 prop 换算为百分比并填充未分配空间 → pxSizes 映射为像素数组 → useResize 基于像素数组执行拖拽偏移与折叠操作。拖拽只改相邻两个面板的尺寸,其他面板保持不变,这保证了交互的局部性与性能。
拖拽交互细节
分隔条同时支持鼠标与触摸事件(mousedown/mousemove/mouseup 与 touchstart/touchmove/touchend,见 split-bar.vue),触摸场景设置了 touchAction: 'none' 防止页面滚动干扰。拖拽过程中容器还会渲染一层 el-splitter__mask 遮罩(见 splitter.vue),用于防止 iframe 等元素吞掉拖拽事件。当多个分隔条重叠时,useResize 中 onMoving 会向上回溯寻找最近的可拖拽索引(useResize.ts),适配嵌套场景下的命中判断。
最小/最大尺寸与折叠的交互
折叠与尺寸约束共享同一套限制逻辑:被折叠的面板尺寸为 0,再次展开时恢复到折叠前尺寸,但受 min/max 与相邻面板总尺寸的 clamp 约束,展开后的尺寸不会超过可分配范围。这一点与官方文档中"使用 min 属性可以防止折叠后再通过拖拽展开"的说明相呼应——折叠后面板处于 0 尺寸,若其 min 大于 0,拖拽恢复时会立刻被钳制到 min 值。
使用建议与注意事项
- 容器必须有确定尺寸:Splitter 依赖容器尺寸计算百分比,父元素需显式设置宽高,否则面板无法正确布局。
- 尺寸单位约定:
size、min、max支持px、%后缀或裸数字(按像素处理),百分比换算依据 useSize.ts 的isPct/isPx/getPct/getPx实现。 - 性能敏感场景启用
lazy:内容重、更新开销大的面板建议开启懒模式,把高频的resize计算收敛到拖拽结束的一次提交。 - 布局持久化:通过
@resize-end或collapse事件获取的sizes数组可直接序列化保存,下次渲染时作为各面板的size初始值回填。 - 组件版本标注:
lazy属性自 2.11.0 起可用,collapse事件自 2.10.3 起可用,splitterPanelRef自 2.11.9 起可用,使用前请确认依赖版本;整个组件当前仍标记为 beta(^(beta)),API 存在微调可能。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python270
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java311
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java220
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript220
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300