news 2026/9/7 10:10:29

Material UI Badge 组件全解析:计数、圆点、可见性控制与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Material UI Badge 组件全解析:计数、圆点、可见性控制与源码实现

Material UI Badge 组件全解析:计数、圆点、可见性控制与源码实现

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

Material UI 的Badge组件用于在其包裹的子元素右上角生成一个小标记,以承载未读消息数、通知计数、在线状态等补充性状态信息。本文以 Badge 官方文档 为主线,完整覆盖其全部用法指南与演示场景,并深入 组件源码 与 单元测试,讲解max截断、showZero隐藏、不可见过渡动画保留等行为的底层实现,读完后可直接在项目中正确、可访问地落地 Badge 的各种形态。

使用指南:官方给出的三条设计准则

官方文档在 badges.md 中明确了三条使用规范,这三条准则同时决定了组件的语义和可访问性设计:

  1. 徽标只用于补充性状态:Badge 适合展示短小的计数或紧凑状态,例如收件箱按钮上的未读消息数。如果某个状态本身非常重要,应直接展示在界面中,而不是仅依赖徽标传达。
  2. 徽标语义要并入宿主元素的可访问名称:Badge 是依附于另一元素的视觉提示,它的含义应包含在宿主元素的 accessible name 中。例如应使用aria-label="Inbox, 4 unread messages",而不是简单的aria-label="Inbox"
  3. 圆点徽标只用于简单状态variant="dot"不显示文字或数字,只应在周边 UI 已能明确表达状态含义时使用,例如OnlineUnread

官方的 入门演示 就是这三条准则的集中体现:

import IconButton from '@mui/material/IconButton'; import Badge from '@mui/material/Badge'; import MailIcon from '@mui/icons-material/Mail'; const maxVisibleNotifications = 99; const unreadNotificationsCount = 100; function getUnreadNotificationsLabel(count: number) { if (count === 0) { return 'show no unread notifications'; } if (count > maxVisibleNotifications) { return `show more than ${maxVisibleNotifications} unread notifications`; } return `show ${count} unread notification${count === 1 ? '' : 's'}`; } export default function BadgeIntro() { const label = getUnreadNotificationsLabel(unreadNotificationsCount); return ( <IconButton aria-label={label}> <Badge badgeContent={unreadNotificationsCount} color="secondary" max={maxVisibleNotifications} > <MailIcon /> </Badge> </IconButton> ); }

注意其中的关键细节:

  • 徽标数量(100)超过了max(99),因此视觉显示为99+,但aria-label中生成的是 "show more than 99 unread notifications",即可访问名称反映真实语义,而不是被截断后的视觉值
  • aria-label挂在宿主IconButton上而非 Badge 上,因为从源码看,Badge 内部的标记元素默认带有aria-hidden="true"(见下文 源码分析),屏幕阅读器根本不会朗读它。

列表项场景:Badge 挂在 ListItemButton 上

ListItemButton 演示 将同样模式应用到列表中,未读计数被并入列表项的可访问名称:

<ListItemButton selected aria-current="page" aria-label={`Inbox, ${unreadMessagesCount} unread messages`} > <ListItemIcon> <Badge badgeContent={unreadMessagesCount} color="primary"> <InboxIcon /> </Badge> </ListItemIcon> <ListItemText primary="Inbox" /> </ListItemButton>

可见的计数(4)通过模板字符串直接包含在aria-label里,使其在周边 UI 的语境中被朗读。

Badge 内容:badgeContent

使用badgeContent属性在包裹元素上添加短计数或标签。最简示例来自 SimpleBadge:

<IconButton aria-label="show 4 unread messages"> <Badge badgeContent={4} color="primary"> <MailIcon /> </Badge> </IconButton>

badgeContent的类型是React.ReactNode(见 类型定义),因此不仅可以传数字,也可以传字符串或任意节点,如"NEW"

圆点徽标(Dot badge)

使用variant="dot"得到一个不带计数的紧凑状态指示器。DotBadge 演示:

<IconButton aria-label="show new notifications"> <Badge color="secondary" variant="dot"> <NotificationsIcon /> </Badge> </IconButton>

从源码看,variant="dot"会走独立的样式变体:徽标高度、最小宽度收窄为 8px(RADIUS_DOT = 4height/minWidth均为其两倍),内边距归零,见 Badge.js 中的变体定义。同时displayValue会被置为undefined,即dot 变体永远不渲染badgeContent——这一点被 测试用例 "should not render badgeContent when variant='dot'" 明确验证。

可见性:invisible 与 showZero

通过invisible属性可以控制徽标的显隐。BadgeVisibility 演示 用一个 Switch 切换invisible

const [invisible, setInvisible] = React.useState(false); <IconButton aria-label={ invisible ? 'open inbox' : `open inbox, ${unreadMessagesCount} unread messages` } > <Badge color="secondary" badgeContent={unreadMessagesCount} invisible={invisible}> <MailIcon /> </Badge> </IconButton>

值得注意:切换可见性时aria-label也随之变化——徽标隐藏后,可访问名称里不再声称有 4 条未读消息,保持语音播报与视觉一致。

零值自动隐藏:当badgeContent为 0 时,徽标默认自动隐藏;若"0"对界面有意义,可以用showZero属性覆盖此行为。ShowZeroBadge 演示 并排对比了两种写法:

<IconButton aria-label="show no unread messages"> <Badge color="secondary" badgeContent={0}> <MailIcon /> </Badge> </IconButton> <IconButton aria-label="show 0 unread messages"> <Badge color="secondary" badgeContent={0} showZero> <MailIcon /> </Badge> </IconButton>

第一个徽标不可见,第二个显示 "0"。该逻辑的源码实现位于 useBadge.ts:

let invisible = invisibleProp; if (invisibleProp === false && badgeContentProp === 0 && !showZero) { invisible = true; }

也就是说,零值隐藏与invisible是"或"关系:只要badgeContent === 0且未显式开启showZero,徽标就进入不可见状态。测试套件 对这一组合做了穷举验证,包括badgeContent={0}时默认带invisible类、badgeContent={0} showZero时不带invisible类两种断言。

最大值:max

使用max属性为大数字封顶。BadgeMax 演示:

<IconButton aria-label="show 99 unread messages"> <Badge color="secondary" badgeContent={99}> <MailIcon /> </Badge> </IconButton> <IconButton aria-label="show more than 99 unread messages"> <Badge color="secondary" badgeContent={100}> <MailIcon /> </Badge> </IconButton> <IconButton aria-label="show more than 999 unread messages"> <Badge color="secondary" badgeContent={1000} max={999}> <MailIcon /> </Badge> </IconButton>

三个徽标分别显示9999+999+。截断逻辑在 useBadge.ts 中只有一行核心判断:

const displayValue: React.ReactNode = badgeContent && Number(badgeContent) > max ? `${max}+` : badgeContent;

测试用例 精确界定了边界行为:

  • 默认max为 99,badgeContent={100}显示99+
  • badgeContent={1000} max={999}显示999+
  • 等于 max 时不截断badgeContent={1000} max={1000}显示1000
  • 小于 max 时原样显示:badgeContent={50} max={1000}显示50

自定义

颜色(color)

使用color属性把主题调色板颜色应用到徽标上。ColorBadge 演示 展示了三种典型语义配色:

<Badge badgeContent={8} color="primary">...</Badge> // 普通信息 <Badge badgeContent={2} color="success">...</Badge> // 已确认 <Badge badgeContent={1} color="error">...</Badge> // 严重告警

color的合法内置取值为defaultprimarysecondaryerrorinfosuccesswarning(见 PropTypes 定义),默认值为'default'。从源码结构看,徽标背景色的样式变体是动态生成的:Badge.js 遍历theme.palette的所有调色板项(过滤掉contrastText),为每个颜色生成backgroundColor: palette[color].maincolor: palette[color].contrastText。因此只要你在主题中新增了自定义颜色(例如brand),color="brand"会自动获得对应样式,无需手写 CSS——这一点也被OverridableStringUnion类型(Badge.d.ts)在类型层面支持。

对齐(anchorOrigin)

使用anchorOrigin属性把徽标移动到被包裹元素的任意一角。BadgeAlignment 演示 是一个可交互示例,通过两组单选按钮切换verticaltop/bottom)与horizontalright/left),并实时渲染对应的 JSX:

<IconButton aria-label="show 12 unread messages"> <Badge badgeContent={12} color="secondary" anchorOrigin={{ vertical: 'bottom', horizontal: 'left', }} > <MailIcon /> </Badge> </IconButton>

anchorOrigin的默认值为{ vertical: 'top', horizontal: 'right' }(右上角)。从源码看,两个方向都可以只传一半:getAnchorOrigin 会对缺失的维度单独回退默认值——vertical缺省为tophorizontal缺省为right。例如anchorOrigin={{ vertical: 'bottom' }}等价于bottom+right组合,测试 分别断言了这两种单维写法生成的类名(anchorOriginBottomRightRectangularanchorOriginTopLeftRectangular)。

定位本身是通过 CSS 自定义变量注入的,见 Badge.js:

const offset = overlap === 'circular' ? '14%' : '0'; const top = vertical === 'top' ? offset : 'auto'; const bottom = vertical === 'bottom' ? offset : 'auto'; const right = horizontal === 'right' ? offset : 'auto'; const left = horizontal === 'left' ? offset : 'auto'; // ... style: { '--Badge-translate': `${horizontal === 'right' ? '50%' : '-50%'}, ${vertical === 'top' ? '-50%' : '50%'}`, '--Badge-inset': `${top} ${right} ${bottom} ${left}`, '--Badge-origin': `${horizontal === 'right' ? '100%' : '0%'} ${vertical === 'top' ? '0%' : '100%'}`, }

徽标样式侧(Badge.js)消费这些变量:inset: var(--Badge-inset)决定四边贴边距离,translate(var(--Badge-translate))让徽标始终骑跨在元素边缘(水平方向平移 50%、垂直方向平移 ±50%),transform-origin则指向被包裹元素的对应角,保证缩放动画从正确的角点发生。

重叠形状(overlap)

当被包裹元素是圆形(如 Avatar)时,使用overlap="circular"让徽标更贴近弧线。BadgeOverlap 演示 对比了矩形与圆形两种宿主:

<Badge color="secondary" badgeContent={1}> <Rectangle /> </Badge> <Badge color="secondary" overlap="circular" badgeContent={1}> <Circle /> </Badge>

overlap的默认值为'rectangular'。从源码看它只影响一个值:定位 offset 从0(矩形,徽标紧贴直角边缘)变为14%(圆形,徽标内收以匹配圆弧,见 Badge.js L232)。

自定义样式

官方文档指出,可以通过主题样式覆盖(theme style overrides)、sx属性或styled()三种途径自定义徽标,并建议参考项目的自定义化指南。CustomizedBadges 演示 展示了用styled()制作"在线/离线"状态点的完整实现,这是列表头像状态指示的经典写法:

const ContactStatusBadge = styled(Badge, { shouldForwardProp: (prop) => prop !== 'status', })<{ status: ContactStatus }>(({ theme, status }) => { const themePalette = (theme.vars ?? theme).palette; const offlineBadgeColor = theme.vars ? theme.vars.palette.Avatar.defaultBg : theme.palette.grey[400]; return { '& .MuiBadge-badge': { height: 10, minWidth: 10, border: `1px solid ${themePalette.grey[300]}`, boxShadow: `0 0 0 2px ${themePalette.background.paper}`, ...(status === 'offline' && { backgroundColor: offlineBadgeColor, ...(theme.vars ? {} : theme.applyStyles('dark', { backgroundColor: theme.palette.grey[600], })), }), }, }; });

使用时的要点:

<ContactStatusBadge status={contact.status} color={contact.status === 'online' ? 'success' : 'default'} variant="dot" overlap="circular" anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} > <Avatar>{contact.initials}</Avatar> </ContactStatusBadge>
  • shouldForwardProp: (prop) => prop !== 'status'用于阻止自定义的status属性泄漏到 DOM;
  • border: 1px solid grey[300]+boxShadow: 0 0 0 2px background.paper组合出了圆点与头像之间的"描边间隔"效果,而不是把圆点缩小;
  • 离线状态色通过theme.vars分支兼容了 CSS 变量主题(palette.Avatar.defaultBg)与静态主题(grey[400])两套取值路径。

此外,Badge 还暴露了slots/slotProps两个插槽机制,可分别替换rootbadge两个内部节点的组件类型和属性,测试 验证了自定义槽组件的渲染,以及通过slotProps={{ badge: { 'aria-hidden': false, 'aria-label': '10 notifications' } }}覆盖徽标节点默认可访问性属性的能力。

源码深入:组件结构与隐藏机制

双节点结构与 aria-hidden

从 Badge.js 的渲染输出看,Badge 是一个"外壳 + 标记"的双节点结构:

<RootSlot {...rootProps}> {children} <BadgeSlot {...badgeProps}>{displayValue}</BadgeSlot> </RootSlot>
  • root 节点BadgeRoot):position: relative的行内 flexspan,为绝对定位的徽标提供坐标参考,同时透传componentclassName及所有其他宿主属性;
  • badge 节点BadgeBadge):绝对定位的标记本身,硬编码aria-hidden: true(Badge.js L243),并渲染displayValue

测试 hides the visual badge from assistive technologies by default 直接断言了该节点带有aria-hidden="true"属性。这正是官方使用指南第二条"把徽标语义并入宿主元素 accessible name"的结构性原因:徽标本体对辅助技术不可见,若宿主元素不携带计数信息,屏幕阅读器用户将完全丢失该状态

invisible 过渡动画的"状态保留"机制

invisible徽标并不是从 DOM 中移除,而是通过transform: scale(0)收缩消失(Badge.js L124-L133),配合主题过渡曲线实现缩放动画。这里有一个容易被忽略的细节:徽标进入不可见状态的那一帧,colorvariantanchorOriginoverlapbadgeContent可能同时变化(例如未读数归零时从standard切回dot),如果直接用新值渲染,消失动画就会"变形"——从一个新位置的圆形闪到旧位置。

源码的解决方案在 Badge.js:用usePreviousProps缓存上一帧的这五个属性,当徽标处于不可见状态时沿用上一帧的值渲染:

const invisible = invisibleFromHook || (badgeContent == null && variantProp !== 'dot'); // ... const { color = colorProp, overlap = overlapProp, anchorOrigin: anchorOriginPropProp, variant = variantProp, } = invisible ? prevProps : props;

测试 "retains anchorOrigin, content, color, max, overlap and variant when invisible is true for consistent disappearing transition" 专门验证了这一点:从secondary色、dot变体切换为 0 计数、primary色、standard变体、bottom-left锚点时,徽标仍然保留colorSecondarydotanchorOriginTopRightRectangular类名,即以旧形态平滑缩小。

另外注意 invisible 的触发条件是invisibleFromHook || (badgeContent == null && variantProp !== 'dot'):未传badgeContent的 standard 徽标默认不可见,而variant="dot"即使无内容也保持可见(对应测试 L134-L144 中badgeContent={undefined} variant="dot"不带invisible类的断言)。

类名体系

badgeClasses.ts 通过generateUtilityClasses('MuiBadge', [...])生成了完整的工具类名集,前缀为MuiBadge-,主要包括:

类别类名示例触发条件
结构rootbadge始终存在
变体standarddotvariant
状态invisible不可见
颜色colorPrimarycolorSecondarycolorErrorcolorInfocolorSuccesscolorWarningcolor !== 'default'
锚点anchorOriginTopRightanchorOriginBottomLeft等四角组合anchorOrigin
重叠overlapRectangularoverlapCircularoverlap
锚点 × 重叠anchorOriginTopRightCircular等八个组合联合定位

这些类名可用于classes属性覆盖、styled选择器(如演示中的'& .MuiBadge-badge')或主题components.MuiBadge.styleOverrides。需要留意源码中的一处标注:badgeClasses.ts L81 带有TODO: v6 remove the overlap value from these class keys,即anchorOriginTopRightCircular这类"锚点 + 重叠"的组合类名属于历史遗留,未来大版本中组合定位样式将改由overlapCircular与锚点类名组合承担——当前版本使用它们没有问题,但升级时需留意。

Props 速查表

综合 Badge.d.ts 与 PropTypes:

属性类型默认值说明
anchorOrigin{ vertical?: 'top' \| 'bottom'; horizontal?: 'left' \| 'right' }{ vertical: 'top', horizontal: 'right' }徽标锚点,缺省维度单独回退
badgeContentReact.ReactNode徽标内显示的内容
childrenReact.ReactNode徽标所依附的宿主元素
color'default' \| 'primary' \| 'secondary' \| 'error' \| 'info' \| 'success' \| 'warning' \| string'default'主题调色板颜色,支持自定义主题色
invisiblebooleanfalse强制隐藏徽标
maxnumber99显示上限,超过时渲染max+
overlap'rectangular' \| 'circular''rectangular'宿主形状,圆形时徽标内收 14%
showZerobooleanfalse控制badgeContent为 0 时是否隐藏
variant'standard' \| 'dot''standard'标准计数或圆点
componentReact.ElementTypespan替换根节点元素
slots/slotProps见 BadgeSlots{}替换/配置rootbadge插槽
sxSxProps<Theme>系统属性与内联样式
classesPartial<BadgeClasses>类名覆盖

小结与延伸阅读

Badge的行为可以归纳为四条规则链:badgeContent决定显示什么,max决定显示上限(Number(badgeContent) > max时渲染${max}+),showZero/invisible/空内容三者决定显隐,anchorOrigin×overlap通过 CSS 变量决定贴边位置。所有视觉变化都以scale过渡呈现,并在隐藏瞬间保留上一帧的形态参数,保证了消失动画的一致性。

如需继续深入,可从以下仓库文件入手:

  • 组件实现:packages/mui-material/src/Badge/Badge.js
  • 逻辑 Hook:packages/mui-material/src/Badge/useBadge.ts
  • 类型定义:packages/mui-material/src/Badge/Badge.d.ts
  • 测试:packages/mui-material/src/Badge/Badge.test.js
  • 全部官方演示源码:docs/data/material/components/badges/

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 10:08:51

SpringBoot+微信小程序+AI大模型智能校园导航系统毕设项目详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 10:04:31

超市管理系统测试报告实战:用例设计、缺陷管理与质量分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 10:04:25

文生图AI模型:从文本描述到图像生成的技术实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 10:03:14

Bandicam录屏软件实操指南:从编码原理到参数避坑

简介&#xff1a;Bandicam是一款屏幕录制软件&#xff0c;这份绿色便携版压缩包面向游戏实况、教程演示及软件操作讲解等多媒体创作人群&#xff0c;可在保证高画质输出的同时控制视频体积。压缩包共45个文件、约36.41MB&#xff0c;以主程序exe与DLL运行库为核心&#xff0c;搭…

作者头像 李华