Material 篇章收官的两块拼图都很小,但出现在几乎每个页面:图标负责视觉语义,tooltip 负责解释一切放不下的文案。本文覆盖 mat-icon 的字体/SVG 两种模式与注册机制、安全与 CSP 注意事项,以及 mat-tooltip 的定位、延迟、无障碍——最后处理一个容易被忽略的问题:tooltip 与自定义长按手势在触屏上的冲突(呼应第 30 篇)。
mat-icon:字体模式
ng add @angular/material 会在 index.html 引入 Material Icons 字体,之后用连字(ligature)写图标:
<mat-icon>home</mat-icon>
<mat-icon aria-label="搜索">search</mat-icon>
字体模式的原理:home 这几个字符被字体渲染成一个图形。由此推论它的行为:
- 尺寸跟着
font-size走:mat-icon { font-size: 20px; }就是改图标大小 - 颜色跟着
color走:正常设置 CSScolor即可,暗色主题下自动跟随文字色 - 无额外请求:字体文件一次加载,几百个图标都在里面
- 连字名即语义:忘了名字查 [Material Icons/Symbols] 官方列表,用错词就渲染不出图形(显示为文字)
装饰性图标记得 aria-hidden="true"(mat-icon 对空图标默认有此倾向,显式写更稳),功能性图标给 aria-label——这在上面的例子里都有体现。
mat-icon:SVG 模式
品牌 logo、自定义插画不在图标字体里,用 SVG 模式。图标要先注册后使用:
import { Service, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { MatIconRegistry } from '@angular/material/icon';
@Service()
export class AppIcons {
private readonly registry = inject(MatIconRegistry);
private readonly sanitizer = inject(DomSanitizer);
constructor() {
// 单个图标
this.registry.addSvgIcon(
'logo',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg'),
);
// 图标集:一个 SVG 文件含多个 <symbol>,按 name 引用
this.registry.addSvgIconSetInNamespace(
'app',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/app-set.svg'),
);
}
}
<mat-icon svgIcon="logo"></mat-icon>
<mat-icon svgIcon="app:chart"></mat-icon>
注册服务的时机:确保在首个用图标的组件渲染前执行——把 AppIcons 提供在应用级(@Service() 默认 root),并在根组件注入一次即可触发实例化。
SVG 模式的要点:
bypassSecurityTrustResourceUrl只是标记”这个 URL 我信任”,绕过的是 URL 清洗而非 HTML 清洗;只对自己控制的资源使用,不要把用户输入的 URL 注册进来- 图标集内的图形若使用
fill="currentColor",即可像字体图标一样随 CSScolor变色——请设计侧交付时就去色 - 尺寸默认 24px,通过
width/height覆盖(与字体模式的font-size不同,这是两套尺寸逻辑)
两种模式对比
| 维度 | 字体模式 | SVG 模式 |
|---|---|---|
| 图标来源 | 官方图标字体 | 任意 SVG 文件 |
| 加载 | 一次加载全部 | 按需 fetch |
| 尺寸控制 | font-size | width / height |
| 多色支持 | 不支持(单色) | 支持(多色 SVG) |
| 命名空间 | 连字名 | svgIcon + namespace |
| CSP 要求 | 仅字体来源 | 需允许 fetch 图标资源 |
选择很直观:标准 UI 动作用字体模式;品牌资产与多色插画用 SVG 模式。
安全与 CSP
图标相关的 CSP 议题集中两点:
- 字体模式:
index.html引入 Google Fonts CDN 时,style-src/font-src需放行相应域名;内网应用可以把字体文件本地化,彻底不依赖外网 - SVG 模式:
MatIconRegistry通过 fetch 拉取 SVG 文本再内联进 DOM。严格的 CSP 下需要放行对图标资源的请求(同源connect-src 'self'通常即可);如果策略完全禁止运行期资源加载,退回字体模式或构建期把 SVG 直接内联到模板里
另外 mat-icon 对内联的 SVG 内容走 Angular 的安全清洗,不要为了”让某个图标能显示”对图标 HTML 使用 bypassSecurityTrustHtml——出现这种需求时,正确做法是修改图标文件本身(比如去掉内联 script/event handler)。
mat-tooltip:基础与定位
<button matButton [matTooltip]="'按创建时间倒序排列'" matTooltipPosition="below">
排序
</button>
| 输入 | 作用 | 可选值 |
|---|---|---|
matTooltip | 文案(必填) | 字符串或绑定 |
matTooltipPosition | 出现方位 | above / below / before / after |
matTooltipShowDelay | 悬停多久后显示(ms) | 默认 0,建议 200-400 |
matTooltipHideDelay | 移开后多久消失(ms) | 默认 0 |
方位只是”意图”——tooltip 检测到目标贴边、空间不足时会自动换向,不需要你写翻转逻辑。这个能力来自 CDK 的 Overlay 与 position strategy,原理在第 40 篇展开。
全局统一延迟不必逐个绑定:
import { MAT_TOOLTIP_DEFAULT_OPTIONS } from '@angular/material/tooltip';
bootstrapApplication(AppComponent, {
providers: [
{
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: { showDelay: 250, hideDelay: 100 },
},
],
});
无障碍:tooltip 不只是视觉效果
mat-tooltip 的无障碍实现是内置的,但你要正确使用它:
- 自动
aria-describedby:tooltip 显示时,宿主元素会被关联到 tooltip 内容,屏幕阅读器在读出按钮名之后再读描述。你不需要手写任何 aria - 键盘触发:tooltip 宿主获得焦点时同样显示——确保宿主是真的可聚焦元素(button、input、或加
tabindex) - 禁用按钮上的 tooltip 不显示:
disabled的按钮不可聚焦,tooltip 与无障碍信息一起失效。要给禁用态加说明,按钮套一层可聚焦元素,或在旁边用文字说明 - 文案层级:
aria-label/按钮文字回答”这是什么”,tooltip 回答”细节是什么”。不要把操作说明只放进 tooltip——触屏用户悬停不了
手势冲突:tooltip 与长按事件
触屏上 tooltip 的默认行为是长按一段时间后显示。这与第 30 篇实现的 (long-press) 手势正面冲突:用户长按列表项想归档,tooltip 先弹出来挡住界面。
两种解法:
<!-- 方案一:单个元素关闭触屏手势 -->
<li
(long-press)="archive(msg)"
matTooltip="长按归档"
touchGestures="off"
></li>
<!-- 方案二:全局默认关闭,个别元素再开 -->
<li matTooltip="详情" touchGestures="on"></li>
// 方案二对应的全局配置
{
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: { touchGestures: 'off', showDelay: 250 },
}
判断标准:这个交互里长按有没有别的含义。长按有业务语义(多选、归档、呼出菜单)就关掉 tooltip 的触屏手势,让 hover 专属的提示回归桌面端;长按无语义再保留默认。这与原生移动平台的习惯一致——iOS/Android 的 tooltip 类控件同样只在长按无冲突时启用。
顺带一提 matTooltip 与 mat-icon 的组合是最高频用法:
<button matButton="icon" matTooltip="刷新" (click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
图标按钮必须有 tooltip 或 aria-label 之一,两个都有更好——一个服务鼠标用户,一个服务屏幕阅读器。
Material 篇章小结
36 到 39 篇完成了 Material 22 的主干:主题体系(36、37)+ 表单容器(38)+ 图标与提示(39)。组件库里还有表格、导航、对话框等大块头,但它们大多构建在 CDK 的基础设施上——Overlay 管浮层、DragDrop 管交互、VirtualScroll 管性能。下一篇起进入 CDK 篇章,从 Overlay 开始,那是 tooltip、dialog、select、menu 共同的地基。