Ant Design Splitter 面板可折叠(collapsible)能力详解:快捷收缩、动画与拖拽展开限制
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
导读
Splitter 是 Ant Design(antd)布局组件库中用于"自由切分指定区域"的分隔面板组件,支持水平/垂直划分、拖拽调整尺寸以及面板的快捷收起与展开。本文以官方演示 可折叠示例 及其说明文档 collapsible.md 为骨架,系统讲解collapsible配置的正确用法,并结合仓库源码拆解折叠图标的渲染、折叠动画开关、min阈值对拖拽展开的约束机制。读完本文,你将能够为任意面板接入一键收起/展开能力,并理解"折叠后无法通过拖拽展开"这一行为背后的真实源码逻辑。
一、collapsible是什么:为面板提供快捷收缩能力
原文档的核心定义只有一句话:配置collapsible即可为面板提供快捷收缩能力。它属于面板(Panel)级别能力,因此在使用上需要将它配置在Splitter.Panel上,而不是Splitter根组件上。官方 API 文档 index.zh-CN.md 中 Panel 的collapsible参数定义为:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| collapsible | 快速折叠 | boolean \| { start?: boolean; end?: boolean; showCollapsibleIcon?: boolean \| 'auto' } | false |
可以看到,collapsible有三种用法,控制力依次增强:
collapsible={true}:面板同时支持从"分隔条左侧(或上侧)"与"右侧(或下侧)"两个方向被折叠,折叠按钮会出现在该面板两侧对应的分隔条上;collapsible={{ start: true }}:只允许朝start方向折叠(即把当前面板收给相邻面板);collapsible={{ start: true, end: true }}:等价于true的显式写法;collapsible={{ start: true, showCollapsibleIcon: 'auto' }}:在方向开关之上进一步控制折叠图标的显隐策略(5.27.0 起支持)。
折叠图标显隐的三种策略
在 interface.ts 中showCollapsibleIcon的类型被定义为boolean | 'auto',对应的三种渲染模式集中在 SplitBar.tsx 的getVisibilityClass中:
const getVisibilityClass = (mode: ShowCollapsibleIconMode): string => { switch (mode) { case true: return `${splitBarPrefixCls}-collapse-bar-always-visible`; case false: return `${splitBarPrefixCls}-collapse-bar-always-hidden`; case 'auto': return `${splitBarPrefixCls}-collapse-bar-hover-only`; } };true:折叠条始终可见(-collapse-bar-always-visible);false:折叠条始终隐藏(-collapse-bar-always-hidden);'auto':默认策略,仅在鼠标悬停时显示(-collapse-bar-hover-only)。
仓库测试 index.test.tsx 中专门对collapsible的true、{ start: true }、{ end: true }、showCollapsibleIcon: true等各分支做了断言,例如点击.ant-splitter-bar-collapse-start后首个面板尺寸变为 0,验证了图标渲染与点击折叠行为。
二、从示例出发:完整可运行的折叠配置
示例文档 collapsible.md 对应的完整源码是 collapsible.tsx,它是理解collapsible的最佳起点:
import React, { useState } from 'react'; import { Flex, Splitter, Switch, Typography } from 'antd'; import type { SplitterProps } from 'antd'; const Desc: React.FC<Readonly<{ text?: string | number }>> = (props) => ( <Flex justify="center" align="center" style={{ height: '100%' }}> <Typography.Title type="secondary" level={5} style={{ whiteSpace: 'nowrap' }}> {props.text} </Typography.Title> </Flex> ); const CustomSplitter: React.FC<Readonly<SplitterProps>> = ({ style, ...restProps }) => ( <Splitter style={{ boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)', ...style }} {...restProps}> <Splitter.Panel collapsible min="20%"> <Desc text="First" /> </Splitter.Panel> <Splitter.Panel collapsible> <Desc text="Second" /> </Splitter.Panel> </Splitter> ); const App: React.FC = () => { const [motion, setMotion] = useState(true); return ( <Flex vertical gap="middle"> <Flex gap="middle"> <Switch checked={motion} onChange={setMotion} checkedChildren="motion" unCheckedChildren="motion" /> </Flex> <CustomSplitter style={{ height: 200 }} collapsible={{ motion }} /> <CustomSplitter style={{ height: 300 }} orientation="vertical" collapsible={{ motion }} /> </Flex> ); }; export default App;该示例揭示了三个关键事实:
- 折叠能力配置在 Panel 上:两个面板都写了
collapsible(第一个还额外带min="20%"); - 折叠动画配置在 Splitter 根组件上:
collapsible={{ motion }}出现在<Splitter>上,且motion由顶部Switch实时开关。这里的对象类型在 interface.ts 有精确定义——Splitter 级的collapsible结构是{ motion?: boolean; icon?: { start?: ReactNode; end?: ReactNode } },其中icon用于自定义折叠图标,二者均从 6.4.0 版本文档开始出现; - 水平/垂直同时演示:同一套配置通过
orientation="vertical"复用在垂直分割场景(注意旧属性layout已被orientation取代并标记为废弃,见 Splitter.tsx 的warning.deprecated(!layout, 'layout', 'orientation'))。
结合 Switch 动态切换动画
示例把motion做成 state 并接在Switch上,说明折叠动画可以运行时动态开关。在 Splitter.tsx 中,动画是否生效由supportMotion决定:
supportMotion={collapsible?.motion && movingIndex === undefined}即同时满足"开启了motion"且"当前没有正在拖拽分隔条"两个条件时,面板才进入动画模式。而 Panel.tsx 会据此追加panel-transition类,由样式层为尺寸变化提供过渡效果。之所以要求movingIndex === undefined,是因为拖拽过程中的尺寸变化需要即时响应,不应被动画延迟,这一细节体现了"拖拽实时、折叠流畅"的交互设计。
三、折叠的核心交互形态:图标、方向与键盘可达性
折叠图标与默认方向
折叠操作并非直接拖拽分隔条,而是点击分隔条上渲染出的折叠图标(collapse-bar)。图标渲染逻辑见 SplitBar.tsx:水平方向默认使用LeftOutlined(收起 start 侧)与RightOutlined(展开/收起 end 侧),垂直方向则换成UpOutlined/DownOutlined;如果通过collapsible.icon.start / .end(或旧属性collapsibleIcon)传入了自定义节点,则会以自定义内容替换默认图标,并附加-collapse-bar-customize样式类。
折叠按钮的可访问性实现
折叠按钮不是普通<button>,而是带完整 ARIA 语义的div(见 SplitBar.tsx):role="button"、tabIndex={0}、aria-label="Toggle start panel"(或Toggle end panel)。同时它支持键盘操作——onCollapseKeyDown 监听Enter与空格键触发onCollapse(index, type)。这说明折叠功能天然对屏幕阅读器与键盘用户可用,符合 antd 组件的无障碍(a11y)规范。此外,仓库专门有 a11y.test.ts 对 Splitter 的可访问性做覆盖。
四、min阈值的真实作用:折叠后禁止拖拽展开的源码依据
原文档英文部分特别强调:"Can throughminto limit dragging to expand when collapsed"——设置min后,面板处于折叠(size 为 0)状态时将无法通过拖拽分隔条被重新展开。示例中第一个面板collapsible min="20%"正是这一组合的演示:它既能被一键折叠,折叠后又受到min约束,拖拽无法将其撑开。
源码层面的判定逻辑
这一限制的根因并不在拖拽回调里,而在于"是否允许拖拽(resizable)"的推导。见 useResizable.ts:
const mergedResizable = // Both need to be resizable prevResizable && nextResizable && // Prev is not collapsed and limit min size (prevSize !== 0 || !prevMin) && // Next is not collapsed and limit min size (nextSize !== 0 || !nextMin);含义拆解:
- 只有相邻两个面板都
resizable时,它们之间的分隔条才允许拖拽; - 关键在最后两行:当某一侧面板已折叠(
size === 0)且该面板配置了min时,prevSize !== 0 || !prevMin为false,于是mergedResizable整体为false,分隔条被置为不可拖拽,折叠状态因此被"锁住"; - 反之,如果折叠面板没有设置
min(如示例中的第二个面板),则条件重新成立,用户仍可直接拖拽分隔条把折叠面板拖回来。
尺寸限制的百分比换算
示例中的min="20%"是百分比写法。min/max/size同时支持数字 px 与'xx%'字符串两种形态(见 interface.ts 与 API 表的"支持数字 px 或者文字 '百分比%' 类型")。底层在 useSizes.ts 中被统一换算为容器相对比例(getPtg将'20%'解析为 0.2),再供 useResize.ts 在拖拽边界计算中与容器像素值对齐,确保"20%"始终等于当前容器宽高的 20%。
五、点击折叠时的尺寸转移与恢复机制
点击折叠图标后发生了什么?核心算法在 useResize.ts 的onCollapse中:
- 直接折叠:当相邻两侧面板当前尺寸都不为 0 时,将被折叠面板的尺寸清零,并把这段尺寸让渡给相邻面板,同时把原始尺寸缓存到
cacheCollapsedSizeRef:
if (currentSize !== 0 && targetSize !== 0) { // Collapse directly currentSizes[currentIndex] = 0; currentSizes[targetIndex] += currentSize; cacheCollapsedSizeRef.current[index] = currentSize; }再次点击恢复:当目标面板已处于折叠态(尺寸为 0)时进入 else 分支,优先尝试恢复上次折叠前缓存的尺寸(
shouldUseCache会校验该尺寸仍处于min/max允许区间内);若缓存不可用,则在边界约束内取一个安全偏移量把尺寸分回去。这解释了为什么演示中反复点击图标能让面板在"收起/展开"之间往返,并且能大致回到折叠前的宽度。展开-收起回调:折叠动作同时驱动
onResize、onResizeEnd以及 Splitter 级的onCollapse(collapsed, sizes)(见 Splitter.tsx)。其中collapsed是通过nextSizes.map((size) => Math.abs(size) < Number.EPSILON)计算出的布尔数组——尺寸为零(含浮点误差)的面板即被判定为"已折叠",调用方据此可以感知每个面板的折叠状态。
折叠后内容的去留:destroyOnHidden
值得顺带一提的是 Panel.tsx:{!(destroyOnHidden && isCollapsed) && children}——当面板处于折叠态(size为 0)且开启destroyOnHidden时,面板内的 React 子树会被真正卸载(而不是仅靠 CSS 隐藏),适合面板内含播放器、地图等不希望在折叠态空转资源的场景。该配置可写在整个Splitter上(6.4.0 起,影响所有面板),也可在单个 Panel 上覆盖。
六、组合实践:一段可直接落地的配置清单
综合原文档与 API 表,一个生产可用的折叠面板配置可以这样组织:
<Splitter style={{ height: 300 }} collapsible={{ motion: true, icon: { start: <span>‹</span>, end: <span>›</span> } }} onCollapse={(collapsed, sizes) => console.log('collapsed:', collapsed, 'sizes:', sizes)} > {/* 可折叠,min 锁住拖拽展开的边界,折叠内容销毁 */} <Splitter.Panel collapsible min="15%" destroyOnHidden> <Sidebar /> </Splitter.Panel> {/* 仅允许从 start 方向折叠,图标 hover 才显示 */} <Splitter.Panel collapsible={{ start: true, showCollapsibleIcon: 'auto' }}> <Content /> </Splitter.Panel> {/* 完全不参与折叠的只读面板 */} <Splitter.Panel resizable={false}> <StatusBar /> </Splitter.Panel> </Splitter>要点回顾:
- 想在面板上提供收起能力,给
Splitter.Panel加collapsible(boolean 或{ start/end/showCollapsibleIcon }); - 想在全局开启折叠动画与自定义折叠图标,给
<Splitter>传collapsible={{ motion, icon }}(旧版collapsibleIcon属性已废弃,应迁移到collapsible.icon,代码中 Splitter.tsx 会给出弃用警告); - 需要"折叠后禁止拖拽展开",给该 Panel 同时配置
collapsible与min; min/max支持数字 px 或'百分比%',折叠动画默认不开启,需显式motion: true。
结语
collapsible是 Splitter 中实现"可收纳面板布局"的入口能力。围绕官方演示文档,本文补齐了其背后的三层事实:配置上区分 Panel 级折叠开关与 Splitter 级动画/图标;交互上折叠依赖分隔条上的可访问折叠按钮,支持点击与键盘触发;机制上折叠本质是相邻面板间的尺寸转移,而min阈值会通过关闭resizable推导直接锁死折叠态的拖拽展开。需要进一步研究时,可阅读 Splitter 完整 API 文档、折叠行为测试用例、以及折叠算法实现 useResize.ts 与可拖拽判定 useResizable.ts。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考