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包,你可以在新的项目中重用或者把它分享给其 ...
随机推荐
- 【RocketMQ 系列】 RocketMQ 双主双从(同步双写) 集群搭建
1. 各角色介绍 Producer:消息的发送者:举例:发信者 Consumer:消息接收者:举例:收信者 Broker:暂存和传输信息:举例:邮局 NameServer:管理Broker:举例:各个 ...
- 深度解读昇腾CANN模型下沉技术,提升模型调度性能
本文分享自华为云社区<深度解读昇腾CANN模型下沉技术,提升模型调度性能>,作者:昇腾CANN. AI模型的运行通常情况下需要CPU和NPU(昇腾AI处理器)等AI专用处理器协同工作,CP ...
- 8行JS代码实现Vue穿梭框
实现效果 完整 demo 参考 <template> <div class="contain"> <ul class=""> ...
- Linux安装 JDK (CentOS 7)
Linux安装 JDK 一.Linux安装软件的方式 第一种:二进制发布包安装: 软件已经针对具体平台编译打包发布,只要解压,修改配置即可 第二种: rpm安装 : 软件已经按照redhat的包管理规 ...
- Linux 中 Crontab 执行时的环境变量问题(allure命令不执行)
前几天做了UI自动化脚本部署linux服务器,但是放下脚本的allure命令不执行(生成allure报告和启动allure服务的命令不执行),然后就各种找问题,一开始怀疑是allure的环境变量问题, ...
- 【MySQL】MySQL 执行创建存储过程报错的解决方法
源码 创建使用while循环插入数据的存储过程 设置mysql分隔符为// delimiter // drop procedure if exists while1 ; create procedur ...
- java引入es使用
引入依赖 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>el ...
- 界面自动化测试录制工具,让python selenium自动化测试脚本开发更加方便
自动化测试中,QTP和selenium IDE都支持浏览器录制与回放功能,简单的来说就像一个记录操作步骤的机器人,可以按照记录的步骤重新执行一遍,这就是脚本录制.个人觉得传统录制工具有些弊端,加上要定 ...
- blender建模渲染Tips
blender渲染 灯光的三种方式 1,常规灯光:shift+A选择灯光. 2,世界环境光:右侧地球图标调整. 3,物体自发光:把渲染物体变成一个发光体来进行调节灯光. 渲染视窗的调节 ctrl+b裁 ...
- 2024-08-03:用go语言,给定一个从 0 开始的字符串数组 `words`, 我们定义一个名为 `isPrefixAndSuffix` 的布尔函数,该函数接受两个字符串参数 `str1` 和
2024-08-03:用go语言,给定一个从 0 开始的字符串数组 words, 我们定义一个名为 isPrefixAndSuffix 的布尔函数,该函数接受两个字符串参数 str1 和 str2. ...