Element Plus Splitter 分割面板组件完整指南:拖拽布局、折叠与懒加载模式深度解析
【免费下载链接】element-plus🎉 A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus
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 存在微调可能。
【免费下载链接】element-plus🎉 A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考