ngx-bootstrap Tooltip 基础示例(Basic Use-case)完整指南:从场景规格到源码实现

原创2026-10-12 09:49:561,693 阅读
文章标签:UI组件前端

ngx-bootstrap Tooltip 基础示例(Basic Use-case)完整指南:从场景规格到源码实现

导读:本文以 ngx-bootstrap 仓库中 Tooltip 模块的 basic 演示用例(tooltip.examples.basic.use-case.md)为核心,完整还原"Simple demo"按钮悬停显示/移出隐藏的交互场景,并深入源码讲解 TooltipDirective 的触发机制、默认配置与容器渲染原理。读完本文,你将掌握在 Angular(Ivy)项目中快速接入基础 Tooltip 的完整姿势,并能基于同一套机制扩展出位置、延迟、自定义触发、手动控制等进阶用法。

一、Use-case 场景规格:Basic Tooltip 到底在演示什么

本用例是 ngx-bootstrap Tooltip 文档站中最基础、也是所有其他 Tooltip 示例的起点。原文档用标准 use-case 模板描述了它:

要素 内容
Primary Actor(主要参与者) User(用户)
Scope(适用范围) Ngx-bootstrap DEMO / Bootstrap 版本 3 & 4
Goal(目标) 向用户展示 Basic Tooltip 的基础功能

主成功场景(Main success scenario)

  1. 用户打开 Tooltip 演示页面;
  2. 用户点击 Basic 子菜单;
  3. 页面出现一个名为 "Simple demo" 的按钮;
  4. 当用户将鼠标悬停(hover)在 "Simple demo" 按钮上时,一个带指定文本内容的 tooltip 容器显示在按钮上方(top);
  5. 当用户将鼠标光标移离 tooltip 按钮时,tooltip 容器消失。

变体(Variations)

  • 2*:用户也可以通过滚动到达 Basic 子菜单(即除了点击导航,滚动定位到该演示区块同样属于合法入口)。

这份规格虽然简短,却精确锁定了本用例的两个核心验收点:hover 触发显示与移出隐藏,以及默认位置为 top。这两点在后续的源码与测试中都能得到一一印证。

二、最小可运行实现:一个按钮搞定 Basic Tooltip

Basic 演示的落地代码极其精简,全部内容就是 basic.html 中的一个按钮:

<button type="button" class="btn btn-primary"
        tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
  Simple demo
</button>

对应的组件类 basic.ts 甚至不需要任何逻辑:

import { Component } from '@angular/core';

@Component({
  // eslint-disable-next-line @angular-eslint/component-selector
  selector: 'demo-tooltip-basic',
  templateUrl: './basic.html',
  standalone: false
})
export class DemoTooltipBasicComponent {}

这里的关键就是 tooltip="..." 这个属性型指令(attribute directive)。注意它的写法:tooltip 后面直接跟的是普通字符串属性值,而非 [tooltip] 属性绑定。指令内部会自动读取该字符串作为提示内容。

指令的选择器与导出

tooltip 属性对应的是 TooltipDirective:

@Directive({
    selector: '[tooltip], [tooltipHtml]',
    exportAs: 'bs-tooltip',
    standalone: true,
    providers: [
      ComponentLoaderFactory, PositioningService
    ]
})
  • 选择器:[tooltip], [tooltipHtml]——只要元素上存在 tooltip 或(已废弃的)tooltipHtml 属性,指令即生效;
  • exportAs: 'bs-tooltip':允许在模板里用 #myTooltip="bs-tooltip" 拿到指令实例,从而手动调用 show() / hide() / toggle()(见后文"手动控制");
  • standalone:指令本身是独立的,可被 standalone 组件直接导入使用。

三、安装与模块引入

Standalone 组件用法

usage.md 中给出了两种接入方式。对于使用 standalone 组件的新式 Angular 应用:

import { TooltipModule } from 'ngx-bootstrap/tooltip';

@Component({
  standalone: true,
  imports: [TooltipModule,...]
})
export class AppComponent(){}

传统 NgModule 用法

import { TooltipModule } from 'ngx-bootstrap/tooltip';

@NgModule({
  imports: [TooltipModule,...]
})
export class AppModule(){}

TooltipModule 在 tooltip.module.ts 中的定义非常轻量——它只是把指令与容器组件打包并导出指令:

@NgModule({
    imports: [CommonModule, TooltipDirective, TooltipContainerComponent],
    exports: [TooltipDirective]
})
export class TooltipModule {}

提示:文档站的安装入口还提供了 ng add ngx-bootstrap --component tooltip 的 schematics 方式(见 tooltip-section.list.ts),可只安装 Tooltip 组件,避免引入整个库。

四、源码原理:悬停显示、移出隐藏是如何实现的

Basic 场景中"hover 显示 / 移出隐藏"的效果,由两个默认值共同决定,它们都定义在 TooltipConfig 中:

@Injectable({ providedIn: 'root' })
export class TooltipConfig {
  /** sets disable adaptive position */
  adaptivePosition = true;
  /** tooltip placement, supported positions: 'top', 'bottom', 'left', 'right' */
  placement = 'top';
  /** array of event names which triggers tooltip opening */
  triggers = 'hover focus';
  /** a selector specifying the element the tooltip should be appended to. */
  container?: string;
  /** delay before showing the tooltip */
  delay = 0;
}

可以看到:

  • placement = 'top':解释了"tooltip 显示在按钮上方";
  • triggers = 'hover focus':解释了"悬停显示",同时兼顾键盘 focus 无障碍场景。

触发监听与事件解析

在 TooltipDirective.ngOnInit 中,指令通过 ComponentLoader 的 listen 挂载触发器:

this._tooltip.listen({
  triggers: this.triggers(),
  show: () => this.show()
});

triggers 字符串会交给 parseTriggers 解析成一组 {open, close} 事件对。默认 'hover focus' 会展开为 mouseenter → mouseleave 与 focus → blur 两组:鼠标进入按钮时调用 show(),离开时调用 hide();键盘聚焦/失焦同理。

显示与隐藏的核心逻辑

show() 方法中有一个重要的前置守卫:

if (this.isOpen || isDisabledValue || this._delayTimeoutId || !tooltipValue) {
  return;
}

也就是说:没有 tooltip 内容(tooltipValue 为空)时,即使触发事件发生也不会显示任何东西——这正是"Simple demo"按钮必须写 tooltip="..." 内容字符串的原因。随后通过 ComponentLoader 将 TooltipContainerComponent 附加到 container(默认 undefined,即追加到指令宿主附近),并按 placement 定位。

hide() 则先移除 in class 触发淡出,再经 tooltipFadeDuration(默认 150ms)延迟后真正销毁容器。

容器渲染结构

Basic 场景中最终呈现在按钮上方的 DOM,由 tooltip-container.component.ts 的模板生成:

<div class="tooltip-arrow arrow"></div>
<div class="tooltip-inner"><ng-content></ng-content></div>

组件宿主上会动态拼出 show tooltip in tooltip-top bs-tooltip-top top ... 等 class(并兼容 Bootstrap 5 的 PlacementForBs5 映射),role="tooltip" 用于无障碍语义。测试用例正是通过断言 .tooltip-inner 是否存在来判断显示与隐藏(见下文第七节)。

五、核心配置项速查表

以 TooltipConfig 与 TooltipDirective 的输入属性为准,Basic 示例可直接沿用的参数如下:

输入属性 默认值 说明
tooltip — 提示内容,支持字符串或 TemplateRef(动态内容/自定义模板场景)
placement 'top' 位置:top / bottom / left / right,以及 auto 自动适配
triggers 'hover focus' 空格分隔的触发事件列表,如 hover、focus、click,或空字符串表示手动
container 无 容器追加位置的选择器,如 container="body" 可规避 overflow: hidden 父级的遮挡问题
delay 0 显示前的延迟毫秒数(见 TooltipDirective)
isDisabled false 禁用 tooltip
adaptivePosition true 是否开启自适应翻转/防溢出(对应 flip、preventOverflow modifiers)
containerClass '' 追加到 tooltip 容器的自定义 CSS class
boundariesElement 无 防溢出的边界元素:viewport / scrollParent / window

说明:show() 内部会把 adaptivePosition 映射为 positioning 服务的 flip 与 preventOverflow modifiers(见 tooltip.directive.ts),开启后当按钮贴近屏幕边缘时,tooltip 会自动翻转到合适方向,避免超出可视区。

六、从 Basic 出发的进阶延伸

Basic 只是入门。同一演示区块(见 tooltip-section.list.ts)还覆盖了以下可直接复用的变体,它们与 Basic 共享同一套指令与配置体系:

1. 位置(Placement)

placement.html 展示了 top / right / auto / left / bottom 五种取值,只需给按钮加 placement="...":

<button type="button" class="btn btn-default btn-secondary mb-2"
        tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
        placement="auto">
  Tooltip auto
</button>

auto 会让组件自行检测最合适的位置。

2. 点击后消失(Dismiss on next click)

dismiss.html 将 triggers 改为 focus,实现"点击后下次点击时消失":

<button type="button" class="btn btn-success"
        tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
        triggers="focus">
  Dismissible tooltip
</button>

3. 动态内容(Dynamic Content)

将 tooltip 从属性值升级为属性绑定,内容来自组件数据:

<button type="button" class="btn btn-info" [tooltip]="content">
  Simple binding
</button>

4. 手动触发(Manual triggering)

triggers-manual.html 演示了最灵活的用法:triggers="" 关闭自动触发,通过 #pop="bs-tooltip" 拿到指令实例,用 show() / hide() / toggle() 三个方法完全接管显示逻辑:

<span tooltip="Hello there! I was triggered manually"
      triggers="" #pop="bs-tooltip">
  This text has attached tooltip
</span>

<button type="button" class="btn btn-success" (click)="pop.show()">Show</button>
<button type="button" class="btn btn-warning" (click)="pop.hide()">Hide</button>
<button type="button" class="btn btn-info" (click)="pop.toggle()">Toggle</button>

5. 全局配置默认值(Configuring defaults)

config.ts 展示了通过注入 TooltipConfig 覆盖默认值的工厂模式:

import { TooltipConfig } from 'ngx-bootstrap/tooltip';

export function getAlertConfig(): TooltipConfig {
  return Object.assign(new TooltipConfig(), {
    placement: 'right',
    container: 'body',
    delay: 500
  });
}

@Component({
  selector: 'demo-tooltip-config',
  templateUrl: './config.html',
  providers: [{ provide: TooltipConfig, useFactory: getAlertConfig }]
})
export class DemoTooltipConfigComponent {}

这样该组件作用域内所有 tooltip 都会默认靠右显示、追加到 body 并延迟 500ms。

七、测试验证:Basic 场景的行为如何被锁定

Basic 用例"初始不显示、悬停/聚焦后显示、移出后隐藏"的行为,在 tooltip.directive.spec.ts 中有对应的自动化测试:

  • 初始不显示:tooltip should not be displayed until user does not any actions——断言页面中不存在 .tooltip-inner(spec L59-L62);
  • focus 触发显示:tooltip should be displayed by focus event after 0 ms by default——对输入框调用 .focus() 后断言 .tooltip-inner 出现;
  • delay 生效:tooltip should be displayed after specified delay——fakeAsync + tick(1100) 模拟 1000ms 延迟后的显示;
  • mouseenter 触发显示:tooltip should be displayed by mouseenter event——fireEvent(tooltipElement, 'mouseenter') 模拟悬停;
  • 手动 show/hide:点击 #showTooltipBtn / #hideTooltipBtn 按钮分别断言 tooltip 出现与消失。

这些用例正好把 use-case 文档中第 4、5 步(hover 显示、移出消失)翻译成了可回归的断言。

八、无障碍与最佳实践小结

回顾 Basic 场景,有两点值得在实际项目中沿用:

  1. 默认 triggers = 'hover focus':同时覆盖鼠标(hover)与键盘(focus)用户,focus 触发也是实现"点击外部消失"(dismiss)的基础;
  2. 自动 ARIA 关联:指令在显示/隐藏时会自动设置/移除宿主元素的 aria-describedby(见 setAriaDescribedBy),提示内容由容器上的 role="tooltip" 承接,确保屏幕阅读器可感知。

综上,Basic Tooltip 从一份 use-case 规格出发,经 basic.html 一行属性即可接入,其背后的 TooltipDirective 与 TooltipConfig 则以最小默认值提供了可扩展的完整机制——这正是 ngx-bootstrap Tooltip 模块"简单上手、深入可扩展"的设计缩影。

登录后查看全文
ngx-bootstrap