Ant Design Splitter(分隔面板)组件完全指南:布局切分、拖拽缩放、可折叠与受控模式实战
Splitter 是 Ant Design 在布局(Layout)分类下提供的“自由切分指定区域”组件,允许用户通过拖拽分隔条水平或垂直地调整多个面板的大小,并支持最小/最大尺寸约束、面板折叠以及受控模式。本指南以官方中文文档(components/splitter/index.zh-CN.md)为骨架,结合仓库源码与示例,系统讲解其使用场景、全部 API、实现原理与常见实践,帮助你在项目中实现 IDE 式分栏、左右两栏工作台、上下详情区等布局。
何时使用 Splitter
按官方文档,出现以下三类需求时可选用 Splitter:
- 需要水平或垂直地分隔区域;
- 需要自由拖拽调整各区域大小;
- 需要为指定区域设定最大/最小宽高。
它适用于典型的两栏后台工作台、多栏报表编辑区、可收起侧边栏等布局场景。与固定的 Col/Row 栅格不同,Splitter 的面板尺寸可由用户通过分隔条交互式改变。
注意:Splitter 需要通过子元素计算面板大小,因此它的子元素仅支持
Splitter.Panel,其他节点会被过滤。这一约束在入口文件 components/splitter/index.tsx 中通过组合式(Compound)组件挂载Splitter.Panel,并在 useItems 中仅保留合法元素予以保证。
快速上手:基本用法
Splitter 属于复合组件,使用时需在 Splitter 内部渲染两个及以上 Splitter.Panel:
import { Splitter } from 'antd';
const App = () => (
<Splitter style={{ height: 200, boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)' }}>
<Splitter.Panel defaultSize="40%" min="20%" max="70%">
<div>First Panel</div>
</Splitter.Panel>
<Splitter.Panel>
<div>Second Panel</div>
</Splitter.Panel>
</Splitter>
);
该示例对应 components/splitter/demo/size.tsx,要点包括:
- 容器需要显式设置高度(如
height: 200),面板尺寸基于容器实际尺寸计算; defaultSize="40%"表示初始宽度占比 40%,支持像素与百分比两种类型;min/max限制面板可被压缩或拉伸的阈值范围,同样支持数字(px)或'百分比%'字符串。
尺寸归一化的底层原理
从源码看,所有尺寸最终都会被换算为百分比参与布局。useSizes(components/splitter/hooks/useSizes.ts)做三件事:
- 将传入的 px 值除以容器实际尺寸,得到百分比;字符串以
%结尾时通过getPtg直接解析为数值/100; - 统计已有百分比与剩余空间,将未显式设置尺寸的面板按剩余比例分摊;
- 当各面板
min/max无法同时满足时,走贪心分配逻辑。
核心分配算法位于 components/splitter/hooks/sizeUtil.ts 的 autoPtgSizes:它先计算已定义尺寸总和,若总和恰好为 1 且全部有值则按比例缩放;若有未定义项则先尝试平均分摊,超出边界(min/max 累计)时再贪心为未定义面板逐个填充到允许的最大值。这正是“自由面板自动吃满剩余空间”的机制来源。
在渲染层,Panel.tsx 用 Flex 布局实现:面板设置了 size(受控或来自计算)时使用 flexBasis 且 flexGrow: 0,未设置时 flexGrow: 1 自适应;同时注释说明“SSR 首屏时使用 auto”,规避服务端渲染时无容器尺寸导致的计算错位。
布局方向:horizontal 与 vertical
Splitter 默认水平排列(两个面板左右分布,分隔条可左右拖动)。需要上下分布时传入 vertical:
import { Splitter } from 'antd';
const App = () => (
<Splitter vertical style={{ height: 300 }}>
<Splitter.Panel>Top</Splitter.Panel>
<Splitter.Panel>Bottom</Splitter.Panel>
</Splitter>
);
该示例对应 components/splitter/demo/vertical.tsx。
关于方向属性,官方文档需要记住两条规则(自 6.0.0 起):
orientation:'horizontal' | 'vertical',为推荐写法,默认'horizontal';vertical:布尔值,默认false,为旧式便捷写法;layout:与orientation语义相同的旧属性,已废弃(deprecated);- 当
vertical与orientation同时存在时,以orientation优先。
三者最终合并逻辑在 components/splitter/Splitter.tsx 中通过 useOrientation(orientation, vertical, layout) 统一得出方向。开发环境下若仍在使用 layout,控制台会通过 devUseWarning 输出废弃提示(warning.deprecated(!layout, 'layout', 'orientation'))。
面板嵌套与复杂组合
Splitter 支持在面板内部继续嵌套 Splitter,从而构造“左侧一栏 + 右侧上下两栏”这类工作台布局:
import { Splitter } from 'antd';
const App = () => (
<Splitter style={{ height: 300 }}>
<Splitter.Panel collapsible>Left</Splitter.Panel>
<Splitter.Panel>
<Splitter orientation="vertical">
<Splitter.Panel>Top</Splitter.Panel>
<Splitter.Panel>Bottom</Splitter.Panel>
</Splitter>
</Splitter.Panel>
</Splitter>
);
完整示例见 components/splitter/demo/group.tsx。需要提示的是,当 Splitter 嵌套在隐藏的标签页面板等零尺寸容器中时,外层会先跳过尺寸为 0 的容器测量(containerSize === 0 直接返回),避免出现布局异常,相关修复逻辑同样位于 Splitter.tsx 的 onContainerResize 中。仓库内 components/splitter/demo/nested-in-tabs.tsx 即为该场景的示例。
受控模式(sizes 状态同步)
需要把面板尺寸交由 React 状态管理(例如配合“重置”按钮、持久化布局)时,可使用受控写法:把 size 数组的值逐个传给 Splitter.Panel,并通过 onResize 把拖拽后的最新尺寸同步回状态:
import React from 'react';
import { Button, Flex, Splitter, Switch, Typography } from 'antd';
const App = () => {
const [sizes, setSizes] = React.useState<(number | string)[]>(['50%', '50%']);
const [enabled, setEnabled] = React.useState(true);
return (
<Flex vertical gap="medium">
<Splitter onResize={setSizes} style={{ height: 200 }}>
<Splitter.Panel size={sizes[0]} resizable={enabled}>
<Typography.Text>First</Typography.Text>
</Splitter.Panel>
<Splitter.Panel size={sizes[1]}>
<Typography.Text>Second</Typography.Text>
</Splitter.Panel>
</Splitter>
<Flex gap="medium" justify="space-between">
<Switch checked={enabled} onChange={() => setEnabled(!enabled)} />
<Button onClick={() => setSizes(['50%', '50%'])}>Reset</Button>
</Flex>
</Flex>
);
};
完整示例见 components/splitter/demo/control.tsx。受控模式下有几个值得注意的行为:
- 部分受控会产生开发警告:源码中检测到“部分
Panel设置了size、另一部分没有”且未传入onResize时,会提示应补上onResize或将size改为defaultSize; resizable={false}可以关闭单个面板所在分隔条的拖拽能力(对应Splitter.Panel的resizable,默认true);- 受控尺寸同样支持 px 或百分比字符串,跨端回传的值以百分比形式给出。
此外,Splitter 提供了三个拖拽过程回调,便于精确监听:
onResizeStart(sizes):开始拖拽之前触发;onResize(sizes):面板大小变化(拖拽过程中持续触发)时回调;onResizeEnd(sizes):拖拽结束(松手)时回调。
事件接线逻辑集中在 Splitter.tsx 的 onInternalResizeStart / onInternalResizeUpdate / onInternalResizeEnd,它们基于 useResize 对相邻面板做 min/max 联动约束:拖拽第 i 根分隔条时,左/上面板不小于其 min、不大于其 max,同时对称地限制右/下面板,最终“钳制”出合法落点区间。
可折叠(Collapsible)
让面板像侧边栏一样可快速收起/展开,是 Splitter 最常用的增强能力。
面板级折叠
在 Splitter.Panel 上设置 collapsible(默认 false),分隔条两端会出现折叠箭头(单击一次收起相邻面板、再次单击展开):
<Splitter style={{ height: 200 }}>
<Splitter.Panel collapsible min="20%">
First
</Splitter.Panel>
<Splitter.Panel collapsible>
Second
</Splitter.Panel>
</Splitter>
collapsible 支持三种形态(见 interface.ts 中 PanelProps 定义):
boolean:true表示面板左右/上下两个方向都可折叠;{ start?: boolean; end?: boolean }:精细化控制“朝向相邻前一个面板”与“朝向相邻后一个面板”两个方向是否可折叠;- 对象形式下可额外指定
showCollapsibleIcon?: boolean | 'auto'控制折叠图标的显示时机(见下文)。
代码层面对布尔值与对象做了归一化:useItems 的 getCollapsible 会把 true 展开为 { start: true, end: true, showCollapsibleIcon: 'auto' },并保证每个面板都具备完整的折叠描述对象。示例可参考 components/splitter/demo/collapsible.tsx 与多面板场景 components/splitter/demo/multiple.tsx(后者演示了 collapsible={{ start: true }} 仅允许朝一侧收起)。
Splitter 级折叠配置(6.4.0+)
自 6.4.0 起,折叠相关配置集中到了 Splitter 的 collapsible 属性:
| 子项 | 说明 | 类型 |
|---|---|---|
motion |
是否开启折叠动画(开启后动画时长跟随 Component Token 配置) | boolean |
icon.start / icon.end |
自定义折叠图标 | ReactNode |
同时 collapsibleIcon(6.0.0 引入)已废弃,迁移到 collapsible.icon;源码会给出对应废弃警告 warning.deprecated(!collapsibleIcon, 'collapsibleIcon', 'collapsible.icon')。
折叠动画实现细节:Splitter 仅在 collapsible?.motion 开启且当前没有正在拖拽(movingIndex === undefined)时,为面板追加 panel-transition 样式类,使宽度/高度变化产生过渡;面板内部在折叠态(size === 0)会追加 panel-hidden 类。相关判断见 Splitter.tsx 与 Panel.tsx。
折叠方向与分隔条方位相关:在横向布局(horizontal)中,start 折叠的是左侧面板,对应默认图标为 LeftOutlined,end 对应 RightOutlined;垂直布局则上下对应 UpOutlined / DownOutlined,图标选取逻辑在 SplitBar.tsx 中完成,自定义 icon 存在时优先渲染自定义节点。
折叠图标显示策略(showCollapsibleIcon)
Panel.collapsible 对象中的 showCollapsibleIcon 提供 boolean | 'auto' 三种取值(默认 'auto',自 5.27.0 起支持),可见示例 components/splitter/demo/collapsibleIcon.tsx:
true:折叠按钮始终可见(对应样式类collapse-bar-always-visible);false:始终隐藏,但保留键盘操作能力(折叠仍可通过按键触发);'auto':仅 hover 到分隔条时显示(对应collapse-bar-hover-only),是默认体验。
多个相邻面板都声明折叠时,两端按钮的可见模式由 useResizable 汇总取并集(只要有任一方要求显示则显示,都未指定时退回 'auto')。折叠/展开回调为 Splitter.onCollapse(collapsed, sizes)(5.28.0+),其中 collapsed: boolean[] 表示各面板当前是否处于收起(尺寸为 0)状态;触发一次折叠后组件同时会派发一次 onResize 与 onResizeEnd,保证外部受控状态一致。
destroyOnHidden:折叠时销毁面板内容(6.4.0+)
默认折叠面板仅把尺寸置 0,面板 DOM 仍挂载、内容不卸载(便于快速恢复)。若希望折叠时真正卸载内容以节省渲染开销,可设置 destroyOnHidden:
- 在
Splitter上设置:作用于所有面板(默认false); - 在单个
Splitter.Panel上设置:覆盖 Splitter 级别的配置(item.destroyOnHidden ?? destroyOnHidden取面板优先)。
对应实现位于 Panel.tsx:destroyOnHidden && isCollapsed 时不再渲染 children。
延迟渲染模式(lazy,5.23.0+)
对大面板、复杂内容或性能敏感场景,可给 Splitter 加 lazy 开启“预览式”拖拽:
<Splitter lazy style={{ height: 200 }}>
<Splitter.Panel defaultSize="40%" min="20%" max="70%">
First
</Splitter.Panel>
<Splitter.Panel>
Second
</Splitter.Panel>
</Splitter>
示例见 components/splitter/demo/lazy.tsx。开启 lazy 后,拖拽过程中不会实时重排面板,而是在拖拽位置渲染一条半透明“预览条”;鼠标/触摸释放时才一次性提交新尺寸(onOffsetUpdate(..., lazyEnd = true) 触发),从而把昂贵的内容重排从高频 move 事件中剥离出来。
实现要点位于 SplitBar.tsx:
- move 阶段调用
handleLazyMove,先通过getConstrainedOffset把原始位移按ariaMin/ariaMax约束到合法范围,再写入 CSS 变量--bar-preview-offset(transformStyle[varName('bar-preview-offset')])驱动预览条位移; - up/end 阶段调用
handleLazyEnd,一次性提交约束后的偏移量,并将预览条复位; - 预览条仅在使用
window级监听(mousemove/touchmove)拖拽时出现,其样式类为-bar-preview(激活态追加-bar-preview-active)。
双击分隔条重置尺寸(onDraggerDoubleClick,6.3.0+)
自 6.3.0 起,Splitter 提供 onDraggerDoubleClick(index: number) 回调,配合受控状态即可实现“双击分隔条恢复初始尺寸”:
const defaultSizes = ['30%', '40%', '30%'];
const App = () => {
const [sizes, setSizes] = React.useState<(number | string)[]>(defaultSizes);
return (
<Splitter
style={{ height: 200 }}
onResize={setSizes}
onDraggerDoubleClick={() => setSizes(defaultSizes)}
>
<Splitter.Panel size={sizes[0]}>Panel 1</Splitter.Panel>
<Splitter.Panel size={sizes[1]}>Panel 2</Splitter.Panel>
<Splitter.Panel size={sizes[2]}>Panel 3</Splitter.Panel>
</Splitter>
);
};
完整示例见 components/splitter/demo/reset.tsx。实现上,SplitBar 使用原生 onDoubleClick 派发该事件;为防止双击同时误触发拖拽起点,内部用 DOUBLE_CLICK_TIME_GAP = 300 毫秒过滤:两次 mousedown 间隔小于 300ms 时直接忽略(Prevent drag start if it's a double-click action)。
API 总览
通用属性(className、style、前缀类名等)参考 docs/react/common-props 中通用属性约定。以下为官方文档记录的组件完整 API。
Splitter
| 参数 | 说明 | 类型 | 默认值 | 版本 | 全局配置 |
|---|---|---|---|---|---|
| classNames | 用于自定义组件内部各语义化结构的 class,支持对象或函数 | Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> |
- | 6.0.0 | 6.0.0 |
| collapsible | motion 是否开启折叠动画,icon 自定义折叠图标 |
{ motion?: boolean; icon?: { start?: ReactNode; end?: ReactNode } } |
- | 6.4.0 | × |
折叠图标(已废弃,请使用 collapsible.icon) |
{ start?: ReactNode; end?: ReactNode } |
- | 6.0.0 | × | |
| destroyOnHidden | 折叠时(size 为 0)销毁面板内容,应用于所有面板,可在单个面板上覆盖 | boolean |
false |
6.4.0 | × |
| draggerIcon | 拖拽图标 | ReactNode |
- | 6.0.0 | × |
布局方向(已废弃,请使用 orientation) |
'horizontal' | 'vertical' |
'horizontal' |
- | × | |
| lazy | 延迟渲染模式 | boolean |
false |
5.23.0 | × |
| onCollapse | 展开-收起时回调 | (collapsed: boolean[], sizes: number[]) => void |
- | 5.28.0 | × |
| orientation | 布局方向 | 'horizontal' | 'vertical' |
'horizontal' |
6.0.0 | × |
| styles | 用于自定义组件内部各语义化结构的行内 style,支持对象或函数 | Record<SemanticDOM, CSSProperties> | (info: { props }) => Record<SemanticDOM, CSSProperties> |
- | 6.0.0 | 6.0.0 |
| vertical | 排列方向,与 orientation 同时存在时以 orientation 优先 |
boolean |
false |
6.0.0 | × |
| onDraggerDoubleClick | 双击拖拽条回调 | (index: number) => void |
- | 6.3.0 | × |
| onResize | 面板大小变化回调 | (sizes: number[]) => void |
- | - | × |
| onResizeEnd | 拖拽结束回调 | (sizes: number[]) => void |
- | - | × |
| onResizeStart | 开始拖拽之前回调 | (sizes: number[]) => void |
- | - | × |
全局配置列打“√/6.0.0”的项(classNames / styles)支持通过
ConfigProvider的componentConfig.splitter做主题级全局定制(类型声明见 config-provider 使用链与 interface.ts 的语义类型定义)。拖拽图标定制示例可查看 components/splitter/demo/customize.tsx(draggerIcon)与dragger语义样式。
Splitter.Panel
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| collapsible | 快速折叠 | boolean | { start?: boolean; end?: boolean; showCollapsibleIcon?: boolean | 'auto' } |
false |
showCollapsibleIcon: 5.27.0 |
| defaultSize | 初始面板大小,支持数字 px 或文字 '百分比%' 类型 | number | string |
- | - |
| destroyOnHidden | 折叠时(size 为 0)销毁面板内容,覆盖 Splitter 的 destroyOnHidden |
boolean |
- | 6.4.0 |
| max | 最大阈值,支持数字 px 或文字 '百分比%' 类型 | number | string |
- | - |
| min | 最小阈值,支持数字 px 或文字 '百分比%' 类型 | number | string |
- | - |
| resizable | 是否开启拖拽伸缩 | boolean |
true |
- |
| size | 受控面板大小,支持数字 px 或文字 '百分比%' 类型 | number | string |
- | - |
关于 min / max / size / defaultSize 的单位换算,再次强调一个易错点:数字一律按 px、带 % 的字符串按百分比解释,且同一根分隔条两侧的约束会共同作用——实际可拖拽区间是两侧 min/max 交集钳制后的结果(见 Splitter.tsx 中 ariaMinStart/ariaMinEnd、ariaMaxStart/ariaMaxEnd 的取最大/最小运算)。
Semantic DOM:语义化结构与样式定制(6.0.0+)
Splitter 的 classNames / styles 支持对组件内部语义化节点进行精准定制,且支持对象或函数两种形态(函数接收 { props },可在合并后的 props 基础上动态返回)。可用语义节点如下:
| 节点 | 说明 |
|---|---|
root |
根元素,承载 flex 布局、宽高、对齐与拉伸样式 |
panel |
面板元素,承载 flex basis、增长比例与面板容器样式 |
dragger |
拖拽控制元素,可细分 { default, active } 两个状态 |
其中 dragger 比较特殊:直接写字符串等价于给 default 态,完整写法为:
<Splitter
classNames={{
root: 'my-splitter',
panel: 'my-panel',
dragger: { default: 'my-dragger', active: 'my-dragger-active' },
}}
styles={{
dragger: {
default: { background: '#eee' },
active: { background: '#1677ff' },
},
}}
>
...
</Splitter>
源码通过 useMergeSemantic 与 useSemanticRootStyle 实现上下文、props 与 rootClassName 的逐层合并,并把 dragger: 'a' 自动归一化为 { default: 'a' }(见 Splitter.tsx 中的配置项 _default: 'default')。交互细节上,active 态在拖拽进行中(movingIndex === index)生效,而 default 态是常态样式。官方语义化示例见 components/splitter/demo/_semantic.tsx 与 components/splitter/demo/style-class.tsx。
主题变量(Design Token)
Splitter 暴露一组组件级 Design Token,可通过主题算法或 ConfigProvider 定制分隔条的视觉尺寸。从 components/splitter/style/index.ts 的类型定义可看到以下 Token:
| Token | 说明 | 备注 |
|---|---|---|
splitBarDraggableSize |
拖拽标识元素大小 | 推荐使用 |
splitBarSize |
拖拽元素显示大小 | - |
splitTriggerSize |
拖拽触发区域大小 | 决定可命中拖拽的热区宽度 |
resizeSpinnerSize |
拖拽标识元素大小 | 已废弃,请使用 splitBarDraggableSize |
这些 Token 被注册进主题组件接口(见 components/theme/interface/components.ts 中 Splitter?: SplitterComponentToken),因此会出现在组件的 <ComponentTokenTable component='Splitter' /> 表格中,供主题面板实时查看与覆盖。需要说明的是,样式函数内部对未定义 Token 有代码级兜底(splitTriggerSize || 6、splitBarDraggableSize ?? resizeSpinnerSize || 20),具体全局默认值仍以主题实际派发为准。
无障碍与键盘操作
Splitter 的分隔条以语义化元素渲染,内置基础无障碍支持(见 SplitBar.tsx):
- 拖拽条使用
role="separator",并携带aria-valuenow/aria-valuemin/aria-valuemax(以百分比形式表示当前分隔位置与可拖动区间); - 方向为横向时
aria-orientation="vertical"(分隔条把空间切成左右两份,语义上与原生 vertical separator 一致),垂直布局相反; resizable={false}时追加aria-disabled;- 折叠按钮使用
role="button"+tabIndex={0},支持 Enter 与空格键触发折叠(onCollapseKeyDown),并带有aria-label; - 拖拽期间容器会渲染一块透明的
-mask遮罩,防止光标移出组件导致的状态丢失。
源码结构速览与扩展指引
若要在源码层面继续深入,可参考以下文件:
- 组件组装与逻辑编排:components/splitter/Splitter.tsx(方向合并、事件分发、语义样式合并、分隔条 aria 计算);
- 组合式入口与类型:components/splitter/index.tsx、components/splitter/interface.ts;
- 面板与分隔条渲染:components/splitter/Panel.tsx、components/splitter/SplitBar.tsx;
- 尺寸/约束/拖拽核心 Hooks:useSizes、sizeUtil、useResize、useResizable、useItems;
- 样式与主题 Token:components/splitter/style/index.ts;
- 测试用例(覆盖受控尺寸、折叠、lazy、语义样式、SSR、无障碍等):components/splitter/tests 下的
size.test.tsx、lazy.test.tsx、semantic.test.tsx、a11y.test.ts、ssr.test.tsx。
结语
Splitter 是一个把“尺寸计算、拖拽手势、折叠动效、无障碍与主题定制”完整封装在一个复合组件中的布局利器。使用时只要记住四条主线即可快速上手:方向用 orientation(避免 layout)、尺寸约束用 min/max + defaultSize、受控同步用 size + onResize、折叠能力用 Panel.collapsible 与 Splitter.collapsible.motion;性能敏感场景再叠加 lazy 与 destroyOnHidden。结合本文对照仓库内示例与源码,即可在项目中稳定落地各种可拖拽分栏布局。
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 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python310
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46467
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20043
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java33951