首页
/ Ant Design Drawer 关闭按钮位置自定义:closable.placement 的 start/end 完整实战指南

Ant Design Drawer 关闭按钮位置自定义:closable.placement 的 start/end 完整实战指南

2026-09-07 12:36:59作者:董灵辛Dennis

导读

在 Ant Design 的 Drawer(抽屉)组件中,关闭按钮默认出现在面板头部(Header)的起始位置,即 LTR 布局下的左上角。而在 "信息预览抽屉""表单抽屉"等常见业务场景中,开发者往往希望把关闭动作放到右上角,与标题、extra 操作区形成更符合阅读动线的布局。本文以官方演示文档 closable-placement.md 为核心,系统讲解通过 closable={{ placement: 'end' }} 自定义关闭按钮位置的方法,并结合 DrawerPanel.tsxuseClosable.tsxDrawer.test.tsx 等源码,讲透其底层生效机制、参数优先级与样式原理。读完本文,你将能熟练配置 Drawer 关闭按钮的左右位置、图标、禁用态,并通过 ConfigProvider 实现全局统一配置。

一、需求背景:为什么关闭按钮需要"换边"

官方演示文档 closable-placement.md 用一句话点明了该特性的核心价值:

自定义抽屉的关闭按钮位置,放到右侧,默认为左侧。(Drawer with closable placement, customize the close placement to the end, defaults to start.)

也就是说,Drawer 的 closable 属性从 5.28.0 版本起支持通过对象形式传入 placement 字段,取值只有两个:

取值 含义(LTR 默认方向下) 说明
start 左/起始端 默认值,关闭按钮位于 Header 起始位置,即标题左侧
end 右/结束端 传入后关闭按钮移动到 Header 结束端,位于标题/操作区右侧

需要强调:start / end逻辑方向而非物理方向。组件内部通过 marginInlineStart/End、flex 顺序等逻辑属性实现,因此在 RTL(如阿拉伯语)布局下会自动镜像,无需额外适配。

二、最小可运行示例(官方 Demo 完整源码)

该演示的完整组件源码位于 closable-placement.tsx,代码如下,可直接复制运行:

import React, { useState } from 'react';
import { Button, Drawer } from 'antd';

const App: React.FC = () => {
  const [open, setOpen] = useState(false);

  const showDrawer = () => {
    setOpen(true);
  };

  const onClose = () => {
    setOpen(false);
  };

  return (
    <>
      <Button type="primary" onClick={showDrawer}>
        Open
      </Button>
      <Drawer
        title="Drawer Closable Placement"
        closable={{ placement: 'end' }}
        onClose={onClose}
        open={open}
      >
        <p>Some contents...</p>
        <p>Some contents...</p>
        <p>Take a look at the top-right corner...</p>
      </Drawer>
    </>
  );
};

export default App;

运行后点击 "Open" 按钮,抽屉从右侧滑出,正文提示语 "Take a look at the top-right corner..." 也直接点明:关闭按钮现在出现在右上角。示例中的三个关键点值得注意:

  1. open 为受控状态,由 Button 的 onClick 打开,抽屉内关闭按钮与遮罩点击最终都收敛到 onClose 回调;
  2. closable 从布尔值升级为对象 { placement: 'end' },这是移动按钮位置的关键写法;
  3. onClose 中通过 setOpen(false) 关闭抽屉,形成标准的受控闭环。

三、closable 参数全景:布尔值之外的完整能力

很多人对 closable 的印象停留在"是否显示关闭按钮",实际上它支持布尔值 + 配置对象两种形态。官方 API 文档(见 index.zh-CN.md)给出了完整定义:

closable?: boolean | { closeIcon?: React.ReactNode; disabled?: boolean; placement?: 'start' | 'end' }

默认值为 true,各形态能力对照如下:

写法 效果 版本
closable / closable={true} 显示默认关闭按钮(CloseOutlined),位于 start 全版本
closable={false} 隐藏关闭按钮 全版本
closable={{ closeIcon: <Icon /> }} 自定义关闭按钮图标 早期已支持
closable={{ disabled: true }} 关闭按钮禁用(不可点击) 早期已支持
closable={{ placement: 'end' }} 将关闭按钮移动到 Header 结束端 placement 自 5.28.0
closable={{ placement: 'start' }} 显式声明保持默认的 start 位置 5.28.0

三点使用细节:

  • 多个字段可自由组合,例如 closable={{ placement: 'end', closeIcon: <CloseCircleOutlined /> }} 表示"换到右侧 + 换图标";
  • closeIconclosable 联动:官方同时保留了顶层独立的 closeIcon 属性作为推荐用法(文档注释建议 "Recommend to use closeIcon instead",见 DrawerPanel.tsx),二者效果等价;
  • 配置对象与全局配置的优先级:Drawer 实例上的 closable 对象会整体覆盖 ConfigProvider 中下发的全局配置(下文第五节详解)。

附加说明:loading、footer、extra 等不受影响

placement 只影响关闭按钮在 Header 内的摆放,不会改变 Drawer 的其他行为。Header 的显示条件是"有标题、可关闭、有 extra 任一成立",这一点可以在 DrawerPanel.tsxrenderHeader 中看到完整实现:

if (!hasTitle && !mergedClosable && !hasExtra) {
  return null;
}

四、源码级原理:placement 是如何一步步生效的

理解了 API 之后,我们从源码层面追一遍 closable={{ placement: 'end' }} 的完整链路,相关实现集中在 DrawerPanel.tsx

4.1 第一步:解析出 closablePlacement

DrawerPanel.tsx 用一个 useMemo 将 props 或全局 context 中的 closable 归一化为 'start' | 'end' | undefined 三态:

const closablePlacement = React.useMemo<'start' | 'end' | undefined>(() => {
  const merged = closable ?? contextClosable;
  if (merged === false) {
    return undefined;          // 完全隐藏,无位置可言
  }
  if (isPlainObject(merged) && merged?.placement === 'end') {
    return 'end';              // 显式要求放到结束端
  }
  return 'start';              // 其余情况一律默认 start
}, [closable, contextClosable]);

注意这里 merged === false 时返回 undefined——正因为关闭按钮彻底隐藏时才不存在位置问题,这是一个值得品味的边界处理。

4.2 第二步:生成带位置的关闭按钮 DOM

customCloseIconRenderDrawerPanel.tsx)负责把最终计算出的图标包进一个 <button type="button"> 中,其 onClick 直接绑定 onClose。类名生成逻辑是关键:

className={clsx(
  `${prefixCls}-close`,
  {
    [`${prefixCls}-close-${closablePlacement}`]: closablePlacement === 'end',
  },
  mergedClassNames.close,
)}

也就是说:默认 start 位置的按钮不添加额外修饰类,只有切换到 end 时才追加 ant-drawer-close-end 类。这与样式文件 style/index.ts 中的规则一一对应:

[`&${componentCls}-close-end`]: {
  marginInlineStart: marginXS,       // end 时左侧留白
},
[`&:not(${componentCls}-close-end)`]: {
  marginInlineEnd: marginXS,         // start 时右侧留白
},

两种状态都使用 marginInline* 逻辑属性,再一次印证了"start/end 是逻辑方向、自动适配 RTL"的设计。

4.3 第三步:按位置渲染进 Header 的不同插槽

最终的位置差异体现在 renderHeader 的 JSX 结构上。Header 内部实际有三个内容槽位:

<div className={`${prefixCls}-header-title`}>
  {closablePlacement === 'start' && mergedCloseButton}
  {hasTitle && <div className={`${prefixCls}-title`}>{title}</div>}
</div>
{hasExtra && <div className={`${prefixCls}-extra`}>{extra}</div>}
{closablePlacement === 'end' && mergedCloseButton}
  • placement: 'start'(默认):关闭按钮渲染在 header-title 内部、标题之前,视觉上位于左上角;
  • placement: 'end':关闭按钮被渲染到 extra 之后,作为 Header 的最后一个子节点,视觉上位于右上角。

这就解释了为什么 extra 与 end 位置的关闭按钮可以"和平共处":extra 在中间、关闭按钮在其右,二者不会互相覆盖。样式层面 Header 容器本身是 display: flex; align-items: centerheader-title 占据 flex: 1 负责把右侧内容推到最右(见 style/index.ts)。

4.4 幕后的合并逻辑:useClosable

Drawer 面板并没有直接渲染 closable,而是调用了组件库 _util 下的共享 Hook useClosableuseClosable.tsx),它同时被 Dialog/Modal 等组件复用。核心的 mergeClosableConfigs 实现了一个清晰的优先级链:

实例 props > ConfigProvider context > fallback(默认值)

useClosable.tsx

  • 实例传入 closable={false} 时直接短路返回 false(优先级最高);
  • 实例传入对象时,通过 mergeProps 将 fallback、context、prop 三级配置浅合并;
  • 都未传时回退到默认的 { closeIcon: <CloseOutlined />, closable: true }

该 Hook 同时处理了 closeIcondisabled 以及 aria-label(默认 "Close",取自 locale),最终返回 [closable, closeIcon, closeBtnIsDisabled, ariaOrDataProps] 四元组,closeBtnIsDisabled 会被 cloneElement 注入到关闭按钮的 disabled 属性上(DrawerPanel.tsx)。

五、更进阶:通过 ConfigProvider 做全局默认配置

closable.placement 不仅能在单个 Drawer 上生效,还能下沉到 ConfigProvider 做全局默认。根据 API 表格中的"全局配置"列:closable5.15.0 起支持全局配置,其中 placement 字段的全局支持自 6.1.1 起可用。源码层面,DrawerPanel.tsx 正是先通过 useComponentConfig('drawer') 取出全局 context,再与实例 props 合并的:

const merged = closable ?? contextClosable; // 实例优先,context 兜底

官方测试用例 Drawer.test.tsx 完整验证了这条覆盖规则,共三个分支:

  1. 全局配置 closable: { placement: 'end' } 时,渲染结果中出现 .ant-drawer-close-end 元素;
  2. 全局为 end、但某个 Drawer 实例显式传 closable={{ placement: 'start' }} 时,该实例不再出现 .ant-drawer-close-end(实例覆盖全局);
  3. 多层嵌套 ConfigProvider 同理遵循就近覆盖原则。

实际接入方式与普通全局组件配置一致,例如:

import { ConfigProvider, Drawer } from 'antd';

<ConfigProvider
  drawer={{ closable: { placement: 'end', closeIcon: <CloseCircleOutlined /> } }}
>
  {/* 所有未单独声明 placement 的 Drawer 都默认关闭按钮在右上角 */}
  <Drawer open={open} onClose={onClose}>
    ...
  </Drawer>
</ConfigProvider>;

六、回归保障:测试如何守护这一特性

除上述 ConfigProvider 用例外,Drawer.test.tsx 还从多个维度守护关闭按钮位置特性,可作为阅读与二次开发的参照:

测试点 位置 断言内容
基础显示 L279-L305 closable 为 true 时正常渲染关闭按钮,并支持自定义 closeIcon
end 位置 L657-L666 closable={{ placement: 'end' }} 时页面中出现 .ant-drawer-close-end 元素
start 位置 L642-L652 placement: 'start' 时不产生 .ant-drawer-close-start/.ant-drawer-close-end 修饰类
隐藏场景 L170-L172 closable={false} 时按钮完全消失
无障碍属性 L504 通过 closable 支持 aria-* 与 closeIcon

测试大量使用 DOM 查询而非快照,说明该特性对外表现为可被选择器命中的稳定类名结构,这为业务方按 .ant-drawer-close-end 做自定义样式覆盖提供了保证。

七、实践要点与版本注意事项

汇总本特性的最佳实践与踩坑提醒:

  1. 版本前提closable.placement 自 antd 5.28.0 引入(对应 index.zh-CN.md 中 demo 声明 version="5.28.0")。低于该版本请升级后再使用,否则对象中的 placement 字段不会生效。
  2. 只想隐藏按钮:用 closable={false},比传空对象更符合语义,也能让 useClosable 走短路分支,减少一次合并计算。
  3. 按钮禁用态closable={{ disabled: true }} 会给关闭按钮注入 disabled 并保持可见(样式上 pointerEvents: none),适合"必填校验未通过不允许关闭"之类的场景。
  4. 与 extra 的关系:end 位置的关闭按钮会排在 extra 之后、位于最右端;若希望按钮在 extra 左侧,需自行通过 extra 组织操作区。
  5. RTL 无需操心:start/end 是逻辑方向,样式基于 marginInline* 与 flex 顺序实现,配合 Drawer 根节点的 ant-drawer-rtl 类自动镜像。
  6. 无标题仅关闭按钮:当只传 closable={{ placement: 'end' }} 而不传 title/extra 时,Header 仍会保留并添加 ant-drawer-header-close-only 修饰类,确保只有一个关闭按钮时布局不塌陷(DrawerPanel.tsx)。

八、延伸阅读

如果希望继续深入 Drawer 相关主题,可在当前仓库中查看:

  • 组件 API 全表与主题变量:index.zh-CN.md(英文版见 index.en-US.md);
  • 其他官方演示:关闭按钮换位常与 extra.tsx(右上操作区)、placement.tsx(抽屉方向)、mask.tsx(遮罩点击关闭)组合使用;
  • 底层 rc-drawer 封装层:Drawer.tsx(负责 mask、motion、zIndex、push 等能力);
  • 组件级全局配置机制:ConfigProviderdrawer 组件配置入口,可结合 config-provider 文档阅读。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391