CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / ANGULAR / P-201 · Angular 高级教程

Angular Material 22+ 教程 39:Icon 与 Tooltip

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-sizemat-icon { font-size: 20px; } 就是改图标大小
  • 颜色跟着 color:正常设置 CSS color 即可,暗色主题下自动跟随文字色
  • 无额外请求:字体文件一次加载,几百个图标都在里面
  • 连字名即语义:忘了名字查 [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",即可像字体图标一样随 CSS color 变色——请设计侧交付时就去色
  • 尺寸默认 24px,通过 width/height 覆盖(与字体模式的 font-size 不同,这是两套尺寸逻辑)

两种模式对比

维度字体模式SVG 模式
图标来源官方图标字体任意 SVG 文件
加载一次加载全部按需 fetch
尺寸控制font-sizewidth / 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 类控件同样只在长按无冲突时启用。

顺带一提 matTooltipmat-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 共同的地基。

系列导航

← 算法 039:字符串匹配:文本预处理:后缀树(Suffix Tree) 目录 算法 040:大数据处理:Overview →
← 返回文章列表