Angular Material 18+ 高级教程 – Material Tooltip
前言
一个常见的 Tooltip 使用场景是

当有 ellipsis 时,hover 显示全文。
Tooltip 算是一种 Popover,我们之前有讲过,要搞 Popover 可以使用底层的 CDK Overlay 来实现。
而 Angular Material Tooltip 便是基于 CDK Overlay 实现的。
基本用法
首先,我们有一个 ellipsis。
<div class="container">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!</p>
</div>
Styles
.container {
margin-top: 64px;
margin-inline: auto;
max-width: 360px;
border: 1px solid black;
padding: 16px;
p {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
}
效果

后半段的内容被省略了,我们希望 hover 它能显示全文。
MatTooltip 指令
import MatTooltipModule 或者 MatTooltip 指令 (它是 Standalone Component,所以也可以单独使用)

接着 App Template
<div class="container">
<p matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!" >
Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!
</p>
</div>
效果

hover 会立刻 popup Tooltip,如果是 touch device 则是 long press 500ms 会 popup Tooltip。
Tooltip 优先选择 popup 在下方 (CDK Overlay Preferred Positions 概念)。
如果字太长 (超过 200px width) 它会 break to multiple line。
Options
我们可以通过一些 options 控制它的体验。
Delay show / hide
by default,hover 会立刻 popup Tooltip,如果我们希望它慢一点可以传入 @Input matTooltipShowDelay 或 matTooltipHideDelay
<p
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
matTooltipShowDelay="1000"
matTooltipHideDelay="1000"
>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!
</p>
这样 show / hide 就会 delay 1 秒 (1000 milliseconds)。
效果

Disabled
<p
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
matTooltipDisabled
>
也是通过 @Input
配上 signal 是这样
<p
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
[matTooltipDisabled]="disabled()"
>
App 组件
export class AppComponent {
readonly disabled = signal(true);
}
Preferred positions
我们可以选择其中一个位置作为优先 popup 的地方,by default 是 below。

before 和 after 是 Logical Properties 概念,LTR (left to right) 情况下 before 指的是 left,after 指的是 right。
<p
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
matTooltipPosition="left"
>
效果

虽然只能设置一个 position (不像 CDK Overlay 的 withPositions 可以放多个),但是它内部其实是有多个 preferred positions,
所以也会兼顾空间不足的情况。
Position at origin

by default,Tooltip 会定位在 <p> 的外面,而且和我们 hover 的地方无关。
而开启 position at origin 后,Tooltip 会依据我们 hover 的地方做显示 Tooltip。
<p
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
matTooltipPositionAtOrigin
>
效果

Programmatic show / hide
想用 JS 控制 show / hide 也行
<p
#tooltip
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
>
加上 #tooltip template variables,然后 query MatTooltip 调用 show / hide 方法就可以了
export class AppComponent {
readonly tooltip = viewChild.required('tooltip', { read: MatTooltip });
constructor() {
afterNextRender(() => {
window.setTimeout(() => this.tooltip().show(), 1000);
window.setTimeout(() => this.tooltip().hide(), 3000);
});
}
}
注:如果 Tooltip 处于 disabled 状态,那 show / hide 方法会失效。
Overriding styles
Tooltip by default 超过 200px 就会 break to multiple line,如果我们不喜欢,可以 override 掉它。
首先用 @Input matTooltipClass 添加一个 class
<p
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!"
matTooltipClass="my-tooltip"
>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore, natus!
</p>
@Input matTooltipClass 也支持 ngClass 的写法,因为它内部就是使用了 ngClass。
然后在 styles.scss 写上 override CSS
.mat-mdc-tooltip.my-tooltip .mdc-tooltip__surface {
max-width: unset;
}
Tooltip 是 Overlay 做的,所以它会被 append to body,要修改它的 styles 我们需要把 styles apply to global (e.g. styles.scss)
这是它的 HTML 结构

效果

Global options
上面教的都是用 @Input 挨个设置 options,假如我们想一次性设置所有 Tooltip,那可以使用 Dependency Injection provide MAT_TOOLTIP_DEFAULT_OPTIONS token。
app.config.ts (也可以 provide to specify 组件 override default options)
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
provideAnimationsAsync(),
{
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: {
hideDelay: 0,
showDelay: 0,
position: 'below',
positionAtOrigin: false,
touchGestures: 'auto',
touchLongPressShowDelay: 500,
touchendHideDelay: 1500,
disableTooltipInteractivity: false,
} satisfies MatTooltipDefaultOptions,
},
],
};
上面写的都是默认值。
有几个 options 是无法透过 @Input 设置的,只能透过 MatTooltipDefaultOptions
- touchGestures

它有 3 个选择:'auto' | 'on' | 'off'。
在 touch device 情况下,long press 是触发 Tooltip 的手势。
但是游览器有一些 element 有原生的 long press 行为,这时候 Tooltip 和游览器就撞了。
‘auto’ 表示让游览器赢
‘on’ 表示让 Tooltip 赢
'off' 表示不支持 Tooltip 不支持 touch device
touchLongPressShowDelay
在 touch device 情况下,long press 多久后会 popup Tooltip。
touchendHideDelay
在 touch device 情况下,Tooltip popup 出来后多久它会自动 hide 起来
disableTooltipInteractivity
禁止 Tooltip 交互。
所谓的禁止就是 pointer-events: none

当 mouseleave 遇上 Tooltip

这是一个 sidebar 交互体验。
by right,当 mouse 离开 sidebar 的时候 sidebar 才会关闭。
但是由于 Tooltip 在 body,所以当 mouse 移动到 Tooltip 时,它也算是离开了 sidebar,所以 sidebar 被关闭了。
Reproduction
我们做个单元测试,还原这个问题。
App Template
<div class="container" #container>
<button
matTooltip="Lorem ipsum dolor sit amet consectetur adipisicing elit.
Voluptatibus molestiae et saepe voluptas dolore dolorum cupiditate accusantium
aliquam aut reiciendis laboriosam qui alias quisquam, ab minima adipisci tempore cumque sunt."
>
Submit
</button>
</div>
Styles
.container {
margin-top: 64px;
margin-inline: auto;
max-width: 428px;
border: 1px solid black;
padding: 16px;
button {
background-color: pink;
border-width: 0;
padding: 16px;
border-radius: 4px;
cursor: pointer;
}
}
App 组件
export class AppComponent {
readonly container = viewChild.required<string, ElementRef<HTMLElement>>('container', { read: ElementRef });
constructor() {
afterNextRender(() => {
const container = this.container().nativeElement;
container.addEventListener('mouseenter', () => {
container.style.backgroundColor = 'lightblue';
});
container.addEventListener('mouseleave', () => {
container.style.removeProperty('background-color');
});
});
}
}
当 container mouse enter 就给它一个浅蓝色,mouse leave 就拿掉浅蓝色。
效果

可以看到,当 mouse 移动进 Tooltip (button 和 tooltip 之间白色的区域也是 tooltip 范围来的) 时,浅蓝色被拿掉了,
因为 Tooltip 在 body,不在 container 内,所以 mouse 算是离开 (mouse leave) 了 container。
Solution
这种鸟问题通常解决方案都不太优雅。首先我们可以先参考 Tooltip 自己怎么处理 hover。

当 mouse leave Tooltip,Tooltip 会消失。
但是,如果 mouse leave 是去到 button (the trigger of Tooltip) 则 Tooltip 不会消失。
我们可以在 trigger 和 Tooltip 之间来回游走,Tooltip 都不会消失。
Tooltip 的源码在 tooltip.ts

Tooltip 组件监听了 mouse leave 事件

relatedTarget 是指离开后,去到了哪一个 element,
如果 relatedTarget 是或包含在 trigger element (button) 里, 那就不会 hide Tooltip。
它会等到 trigger element 触发 mouse leave 而且不是 leave into Tooltip 才会 hide。

好,清楚了,那我们也可以学它,用 relatedTarget 做判断。
export class AppComponent {
readonly container = viewChild.required<string, ElementRef<HTMLElement>>('container', { read: ElementRef });
// 需要加个 class selector 做识别
readonly tooltipClassName = 'my-tooltip';
constructor() {
afterNextRender(() => {
const container = this.container().nativeElement;
// 判断 relatedTarget 是不是 Tooltip
const isTooltip = (relatedTarget: EventTarget | null): relatedTarget is HTMLElement =>
relatedTarget instanceof HTMLElement && relatedTarget.closest(`.${this.tooltipClassName}`) !== null;
// 判断 relatedTarget 是不是 Tooltip 组件
// Tooltip 组件是 Tooltip 的 parent
// 小心坑:
// 直觉上,Tooltip 和 Tooltip 组件应该是同一个 element 才对,或者说 Tooltip 组件只是一个简单的 wrapper 也行。
// 但实际上却不是这样,当我们 mouse leave 时,有些情况会进入到 Tooltip 组件,有些情况则会进入到 Tooltip...
// 所以必须分清楚。
const isTooltipComponent = (relatedTarget: EventTarget | null): relatedTarget is HTMLElement =>
relatedTarget instanceof HTMLElement &&
// 提醒:
// Angular Material 没有公开 Tooltip 组件的信息
// 这里我们只能依据它目前的源码,hardcode 上它的 tag name 做识别。这里未来是可能遭遇 breaking changes 的哦。
relatedTarget.tagName === 'MAT-TOOLTIP-COMPONENT' &&
// 提醒:
// 目前 Tooltip 组件只有一个 child element,那就是 Tooltip。
// 这个结构未来也有可能遭遇 breaking changes
relatedTarget.firstElementChild!.classList.contains(this.tooltipClassName);
// 判断 relatedTarget 是不是 container
const isContainer = (relatedTarget: EventTarget | null): boolean =>
relatedTarget instanceof HTMLElement && container.contains(relatedTarget);
container.addEventListener('mouseenter', ({ relatedTarget }) => {
// 如果 enter 是 from Tooltip 那不需要处理
if (isTooltip(relatedTarget) || isTooltipComponent(relatedTarget)) return;
container.style.backgroundColor = 'lightblue';
});
container.addEventListener('mouseleave', ({ relatedTarget }) => {
// 处理 on container mouse leave
const handleLeaveContainer = () => container.style.removeProperty('background-color');
// 如果是 leave to Tooltip,那不算 leave container
if (isTooltip(relatedTarget) || isTooltipComponent(relatedTarget)) {
const tooltipComponent = isTooltip(relatedTarget) ? relatedTarget.parentElement! : relatedTarget;
// 监听 Tooltip 组件 mouse leave,如果是回来 container 那就 do nothing
// 如果是 leave to 其它地方,那就等价于 leave container 了
tooltipComponent.addEventListener(
'mouseleave',
({ relatedTarget }) => !isContainer(relatedTarget) && handleLeaveContainer(),
{ once: true },
);
return;
}
handleLeaveContainer();
});
});
}
}
效果

目录
上一篇 Angular Material 18+ 高级教程 – CDK Overlay
下一篇 Angular Material 18+ 高级教程 – CDK Table
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular Material 18+ 高级教程 – Material Tooltip的更多相关文章
- Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...
- Pandas之:Pandas高级教程以铁达尼号真实数据为例
Pandas之:Pandas高级教程以铁达尼号真实数据为例 目录 简介 读写文件 DF的选择 选择列数据 选择行数据 同时选择行和列 使用plots作图 使用现有的列创建新的列 进行统计 DF重组 简 ...
- ios cocopods 安装使用及高级教程
CocoaPods简介 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如Java语言的Maven,nodejs的npm.随着iOS开发者的增多,业界也出现了为iOS程序提供依赖管理的工具,它的 ...
- 【读书笔记】.Net并行编程高级教程(二)-- 任务并行
前面一篇提到例子都是数据并行,但这并不是并行化的唯一形式,在.Net4之前,必须要创建多个线程或者线程池来利用多核技术.现在只需要使用新的Task实例就可以通过更简单的代码解决命令式任务并行问题. 1 ...
- 【读书笔记】.Net并行编程高级教程--Parallel
一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机 ...
- 分享25个新鲜出炉的 Photoshop 高级教程
网络上众多优秀的 Photoshop 实例教程是提高 Photoshop 技能的最佳学习途径.今天,我向大家分享25个新鲜出炉的 Photoshop 高级教程,提高你的设计技巧,制作时尚的图片效果.这 ...
- 展讯NAND Flash高级教程【转】
转自:http://wenku.baidu.com/view/d236e6727fd5360cba1adb9e.html 展讯NAND Flash高级教程
- Net并行编程高级教程--Parallel
Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控 ...
- [转帖]tar高级教程:增量备份、定时备份、网络备份
tar高级教程:增量备份.定时备份.网络备份 作者: lesca 分类: Tutorials, Ubuntu 发布时间: 2012-03-01 11:42 ė浏览 27,065 次 61条评论 一.概 ...
- Django 2.0.1 官方文档翻译: 高级教程:如何编写可重用的app (page 13)
高级教程:如何编写可重用的app (page 13) 本节教程上接第七部分(Page 12).我们会把我们的 web-poll应用转换成一个独立的python包,你可以在新的项目中重用或者把它分享给其 ...
随机推荐
- WSL2连接USB设备(以USRP B210为例)
使用WSL2时,发现其无法直接识别到宿主机上插入的USB设备. 可利用USPIPD-WIN项目进行连接. 以下以USRP B210设备连接为例,展示连接过程: 安装USBIPD-WIN 项目 参考连接 ...
- Solo 开发者周刊 (第7期):Sora出世,或许又一行业将会消失?
这里会整合 Solo 社区每周推广内容.产品模块或活动投稿,每周五发布.在这期周刊中,我们将深入探讨开源软件产品的开发旅程,分享来自一线独立开发者的经验和见解.本杂志开源,欢迎投稿. 好文推荐 sor ...
- SQL去重distinct方法解析
来源:https://www.cnblogs.com/lixuefang69/p/10420186.html SQL去重distinct方法解析 一 distinct 含义:distinct用来查询不 ...
- [oeasy]python0141_自制模块_module_reusability_复用性
自制包内容 回忆上次内容 上次导入了外部的py文件 import my_module 导入一个自己定义的模块 可以使用my_module中的变量 不能 直接使用 my_module.py文件中的变 ...
- [oeasy]教您玩转linux0001 - 先跑起来 🥊
Python 什么是 Python? Python 很好用 适合初学者 而且在各个领域都很强大 添加图片注释,不超过 140 字(可选) 后来居上 下图可以点开 添加图片注 ...
- C# RSA加密解密及RSA签名和验证
1.RSA加密解密 (1)获取密钥,这里是产生密钥,实际应用中可以从各种存储介质上读取密钥 (2)加密 (3)解密 2.RSA签名和验证 (1)获取密钥,这里是产生密钥,实际应用中可以从各种存储介质上 ...
- JMeter While循环控制器应用之遍历获取文件参数
While循环控制器应用之遍历获取文件参数 by: 授客 QQ:1033553122 测试环境 JMeter-5.4.1 应用 实现单线程在单次迭代内遍历获取文件参数 说明:上图仅给出关键配置信息 注 ...
- vscode添加python文件头模板
pycharm可以自动生成python的文件头模板,但是vscode目前还不可以(不支持python,c的似乎有插件支持了).琢磨了一下,可以通过用户代码片段来实现. 1. 什么是用户代码片段 参考文 ...
- 一文详解 JuiceFS 读性能:预读、预取、缓存、FUSE 和对象存储
在高性能计算场景中,往往采用全闪存架构和内核态并行文件系统,以满足性能要求.随着数据规模的增加和分布式系统集群规模的增加,全闪存的高成本和内核客户端的运维复杂性成为主要挑战. JuiceFS,是一款全 ...
- RHCA rh442 002 监控工具 脏页 块设备名 缓存
sar 看某一个时间的数据 sar -d 1 5 与iostat类似 计算机识别设备按编号识别 0-15预留出 8 为iscsi设备 做一个块设备名 名字不重要是给人看的,重要的是编号 8 17(主编 ...