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包,你可以在新的项目中重用或者把它分享给其 ...
随机推荐
- C#多窗口切换的实现
本文关键字: 多窗口切换 label splitContainer 窗口背景颜色设置 字体设置 窗口布局 按钮事件 按钮 新建项目: 开发MainForm: MainForm先添加1个splitCon ...
- Git 奇幻之旅⌛️
第一天: 本地仓库 故事的主角是小明,一个刚入门编程的小白.他正在为一个项目写代码,但是他发现每次修改代码都很麻烦,因为他要不断地备份文件,而且很容易弄混版本.有一天,他听说了一个叫 Git 的神奇工 ...
- linux mysql 允许进行远程连接 比如 navicat
出于安全方面考虑默认只允许本机(localhost, 127.0.0.1)来连接访问.所以开启远程访问权限.登录mysqlmysql -uroot -pxxxxxx 1:GRANT ALL PRIVI ...
- SQL SERVER根据数据表的某个栏位查询另一个数据表符合条件的某个栏位的值,如果多行则合并为一张字符串形式
SQL SERVER根据数据表的某个栏位查询另一个数据表符合条件的某个栏位的值,如果多行则合并为一张字符串形式 要在 SQL Server 中根据一个数据表的某个列查询另一个数据表符合条件的某个列的值 ...
- [oeasy]python0033_回车_carriage_return_figlet_字体变大
回到开头 回忆上次内容 进程前后台切换 ctrl + z 把当前进程切换到后台并暂停 jobs 查看所有作业 用 fg 可以把后台进程再切回前台 fg %1 可以把指定的任务切回前台 用 bg 可 ...
- npm和yarn 命令比较
命令比较 npm init | yarn init:创建一个新包 npm run | yarn run:运行 package.json 中定义的脚本 npm test | yarn test:测试一个 ...
- Django+Bootstrip 卡片模板设计 经典精品
下面是一个完整的卡片模板代码,包含所有元素,并使用Django的模板语言来处理状态字段的条件渲染.同时还包括示例视图和URL配置. 完整的卡片模板 <div class="card&q ...
- Vue打包部署到CentOS 7
项目打包 在项目目录下执行打包目录进行打包 yarn build // 或者 npm run build 打包完成后会生成一个dist文件夹,这样就打包完成了(我这样做了SEO的,所有目录结构有点不一 ...
- Meta公司的Llama3大语言模型
Github地址: https://github.com/meta-llama/llama3 官方介绍: https://ai.meta.com/blog/meta-llama-3/ 官方项目主页: ...
- 《Python数据可视化之matplotlib实践》 源码 第三篇 演练 第九章
图 9.1 import matplotlib.pyplot as plt import numpy as np fig=plt.figure() ax=fig.add_subplot(111) f ...