Ant Design Drawer 可拖拽调整尺寸(resizable)完整实战指南
Drawer(抽屉)通常以固定尺寸从屏幕边缘滑出,但当面板内容较长、用户需要更大可视区域或需要自由对比上下文时,固定尺寸会成为体验瓶颈。antd 从 6.0.0 起为 Drawer 引入了 resizable 能力,允许用户直接拖拽面板边缘实时调整抽屉的宽度或高度。本篇将以 resizable 示例文档 及其 示例源码 为主体,结合 Drawer 组件实现源码与样式代码,完整讲解可调整尺寸抽屉的用法、参数语义、受控尺寸同步与拖拽条样式原理,帮助你快速在自己的项目中落地"拖拽改变抽屉大小"的交互。
一、功能定位:从固定尺寸到可拖拽尺寸
普通的 Drawer 通过 size(或已被 size 取代的 width/height)固定面板尺寸,用户无法改变。而开启 resizable 后,抽屉会在可拖拽的边缘(取决于 placement)渲染一条透明的拖拽热区(dragger),用户按住并拖动即可改变面板尺寸。官方对该 demo 的定义是:
可调整大小的抽屉,允许通过拖拽边缘来调整抽屉的宽度或高度。(Resizable drawer that allows users to adjust the drawer's width or height by dragging the edge.)
从源码看,该能力面向横向与纵向四个方向均生效:placement 为 left / right 时拖拽改变的是宽度,为 top / bottom 时拖拽改变的是高度——这正是 API 文档中 maxSize 描述为"可拖拽的最大尺寸(宽度或高度,取决于 placement)"的原因(见 index.en-US.md)。
版本前提:
resizable对象形式自 6.0.0 引入,纯布尔形式resizable自 6.1.0 起可用;maxSize同样为 6.0.0。使用前请确认项目的 antd 版本不低于 6.0.0(示例文档也标注了version="6.0.0")。
二、最小可用示例与逐行解析
resizable.tsx 是一个同时演示"四个方向 + 实时尺寸展示 + 切换方向自动复位"的完整示例,其核心代码如下:
import React, { useState } from 'react';
import type { DrawerProps, RadioChangeEvent } from 'antd';
import { Button, Drawer, Radio, Space } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [placement, setPlacement] = useState<DrawerProps['placement']>('right');
const [size, setSize] = useState(256);
const onChange = (e: RadioChangeEvent) => {
setSize(256); // 切换方向时把尺寸重置为初始值
setPlacement(e.target.value);
};
return (
<>
<Space style={{ marginBottom: 16 }}>
<Radio.Group
value={placement}
onChange={onChange}
options={['top', 'right', 'bottom', 'left'].map((pos) => ({
label: pos,
value: pos,
}))}
/>
<Button type="primary" onClick={() => setOpen(true)}>
Open Drawer
</Button>
</Space>
<div>Current size: {size}px</div>
<Drawer
title="Resizable Drawer"
placement={placement}
onClose={() => setOpen(false)}
open={open}
key={placement}
size={size}
resizable={{
onResize: (newSize) => setSize(newSize),
}}
>
<p>Drag the edge to resize the drawer</p>
<p>Current size: {size}px</p>
</Drawer>
</>
);
};
export default App;
该示例中值得注意的四个细节:
size为受控数值:初始为256,与常见的预设字符串('default'/'large')不同,这里直接以数字作为当前尺寸。size属性支持'default' | 'large' | number | string,其中number会在源码中被直接使用。resizable传入对象并回调onResize:拖拽过程中把最新尺寸写回 state,再通过size={size}回传给 Drawer,形成"拖哪显示哪"的实时受控闭环;页面顶部Current size: {size}px因此能同步显示实时尺寸。key={placement}:切换placement时强制重建抽屉实例,避免面板残留旧方向的状态,是 demo 切换方向的常用手法。- 切换方向先
setSize(256):由于横向(宽)与纵向(高)是两种尺寸语义,demo 在切换方向时主动将尺寸复位,避免上个方向的尺寸直接套用到新方向。
三、相关 API 与 ResizableConfig 详解
可拖拽尺寸涉及的 Drawer 配置项在 API 文档 中有完整定义,下面结合源码逐项说明。
3.1 resizable:是否启用拖拽改变尺寸
resizable?: boolean | DrawerResizableConfig;
- 传入
true即启用最基础的拖拽调整能力,不做任何受控或事件绑定; - 传入
ResizableConfig对象时,可在拖拽生命周期中插入回调(详见 3.3)。
在 Drawer.tsx 中,DrawerResizableConfig 的 TypeScript 定义与官方 ResizableConfig 完全一致,包含三个可选回调。组件接收 resizable 后,通过条件展开透传给底层 @rc-component/drawer:
<RcDrawer
{...rest}
size={drawerSize}
defaultSize={defaultSize}
...
{...(resizable ? { resizable } : {})}
>
也就是说,antd 的 Drawer 是"透传者"角色——真正的拖拽手势、方向判定与尺寸运算由 @rc-component/drawer 完成(DrawerProps 中已通过 Omit 移除底层同名类型,再以 antd 语义重新声明,见 Drawer.tsx)。
3.2 size 与 maxSize:受控尺寸与上限约束
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| size | 预设抽屉宽度(或高度),default 378px、large 736px,或自定义数值/字符串 |
'default' | 'large' | number | string |
'default' |
| maxSize | 可拖拽的最大尺寸(宽度或高度,取决于 placement),用于限制拖拽上限 |
number |
- |
size 的分发逻辑集中在 Drawer.tsx 的 drawerSize 计算中:
size为数字时原样使用(如 demo 中的256);size === 'large'映射为736,size === 'default'映射为常量DEFAULT_SIZE = 378(见 Drawer.tsx);- 数字形式字符串会被
Number()转换; - 对
left/right方向,回退读取旧的width;对top/bottom方向,回退读取旧的height(两者均已标记 deprecated,建议统一改用size)。
因此在实际项目中,若要限制用户拖拽范围,可同时传入初始 size 与 maxSize,例如一个"右侧抽屉最小 378、最大 736"的约束可用 size={378} maxSize={736} resizable 表达。
3.3 ResizableConfig:拖拽生命周期回调
| 参数 | 说明 | 类型 | 版本 |
|---|---|---|---|
| onResizeStart | 开始拖拽调整大小时的回调 | () => void |
6.0.0 |
| onResize | 拖拽调整大小时的回调 | (size: number) => void |
6.0.0 |
| onResizeEnd | 结束拖拽调整大小时的回调 | () => void |
6.0.0 |
三者的应用场景非常清晰:
onResizeStart:进入拖拽态时触发,适合做一次性的 UI 反馈(如给面板加个类名提示"正在调整",或记录拖拽前尺寸用于撤销/还原);onResize:拖拽过程中高频触发,参数为当前像素尺寸。demo 正是靠它驱动setSize实现受控实时显示;若要"松手后再持久化",也可在此做节流记录;onResizeEnd:鼠标/手指松开、本次拖拽结束时触发,适合把最终尺寸持久化(如写入 localStorage 或提交到后端),避免onResize高频写入。
典型用法示例:
const [size, setSize] = useState(520);
<Drawer
open={open}
size={size}
maxSize={900}
resizable={{
onResizeStart: () => setIsDragging(true),
onResize: (newSize) => setSize(newSize),
onResizeEnd: () => {
setIsDragging(false);
// 例如:persistSize(size);
},
}}
/>
四、源码级原理:拖拽指示条(dragger)如何渲染与呈现
当 resizable 开启且抽屉打开时,Drawer 的语义化 DOM 中会多出一个 dragger 结构。测试快照中可看到实际渲染的类名形态(见 demo-extend.test.tsx.snap):
ant-drawer-resizable-dragger ant-drawer-resizable-dragger-right ant-drawer-resizable-dragger-horizontal
类名组合为 ant-drawer-resizable-dragger + 方向 + 轴类型,其中 -right 表示吸附在右侧、-horizontal 表示水平方向的拖拽轴(即调整宽度)。antd 同时把 dragger 纳入语义化样式体系:classNames.dragger 与 styles.dragger 都会在 Drawer.tsx 中合并后传给底层 RcDrawer,因此开发者可用 classNames={{ dragger: 'my-dragger' }} 或 styles={{ dragger: { background: 'red' } }} 精确命中拖拽条。
拖拽条的位置与指针样式在 style/index.ts 中按四方向分别声明,例如:
dragger-left/dragger-right:垂直铺满top: 0; bottom: 0,宽度为draggerSize,cursor: col-resize,吸附在边缘对应侧;dragger-top/dragger-bottom:横向铺满,高度为draggerSize,cursor: row-resize。
基础样式(对所有方向生效)包括:position: absolute、透明背景 transparent、userSelect: none 防止拖拽时选中文本。交互反馈靠两层状态区分(见 style/index.ts):
- 悬停态
&:hover:背景变为colorPrimary且opacity: 0.2,提示"此处可拖拽"; - 拖拽态
&-dragging:背景为colorPrimary且opacity: 0.3,明确反馈"正在调整"。
拖拽热区的宽度来自 token draggerSize,默认值为 4(见 style/index.ts),既足够细而不抢视觉,又保留了够用的命中区域,是抽屉可拖拽交互的"隐形把手"设计。
五、落地实践要点
5.1 让拖拽尺寸在页面其他位置可见
参考 demo 的做法,把 size 提升到公共 state,既作为 Drawer 的受控值,也可用于页面展示(demo 顶部 Current size: {size}px)。注意 onResize 是拖拽期间的实时回调,直接 setState 即可,无需防抖——React 会按帧合并渲染。
5.2 切换方向时重置尺寸
width 与 height 是两套维度,从一个 right 抽屉切到 bottom 抽屉时,若沿用上一次的宽度数值作为高度通常不合理。demo 的策略值得直接复用:切换前 setSize(256),并配合 key={placement} 让抽屉重建。
5.3 尺寸约束
- 用
maxSize限制拖拽上限,避免面板过大挤压主内容; - 拖拽下限取决于抽屉自身的样式约束(如内部内容的最小宽度),不需要额外处理。
5.4 渲染结构引用
由于 dragger 是 Drawer 的语义化子结构,凡是需要自定义拖拽条观感(宽度、颜色、悬停态)或做自动化测试定位(如断言 ant-drawer-resizable-dragger 存在)的场景,都可以直接以该 DOM 类名为锚点。在 antd 自带的测试体系中,该 demo 已通过 demo.test.ts、快照测试与语义测试的多重校验,可当作"用法正确性"的参照实现。
六、小结
一句话总结本指南:resizable + 受控 size + 生命周期回调,即可获得实时、可约束、可持久化的拖拽调整尺寸抽屉。实现上 antd Drawer 将 resizable 透传给 @rc-component/drawer 完成手势计算,自身负责在四个方向渲染对应的 dragger 热区,并通过语义化的 classNames.dragger / styles.dragger 与 token draggerSize 暴露定制入口。若需继续深入,可前往 Drawer.tsx 阅读透传与尺寸计算逻辑,在 style/index.ts 查看拖拽条样式实现,或在 Drawer 完整 API 中查阅 resizable、maxSize、size 的完整说明与其他相关属性。
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 StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00