Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular 自定义指令 Tooltip #118

Open
reng99 opened this issue May 21, 2022 · 0 comments
Open

Angular 自定义指令 Tooltip #118

reng99 opened this issue May 21, 2022 · 0 comments
Labels
angular blog a single blog

Comments

@reng99
Copy link
Owner

reng99 commented May 21, 2022

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

Yeah,关注我的读者应该知道,上一篇文章了解 Angular 开发的内容,我们已经概览了 Angular 的相关内容。在自定义指令的部分,我们已经能够实现编写,但是,在实际场景中,我们还需要标准化的管理。

Angular 是 Angular.js 的升版

So,本文,我们就以 Tooltip 来讲解下自定义指令的内容。

线上效果图,如下:

tooltip_jimmy.png

目录结构

在上一篇文章的实现的代码项目基础上,执行命令行:

# 进入 directives 文件夹
$ cd directives

# 创建 tooltip 文件夹
$ mkdir tooltip

# 进入 tooltip 文件夹
$ cd tooltip

# 创建 tooltip 组件
$ ng generate component tooltip

# 创建 tooltip 指令
$ ng generate directive tooltip

执行完上面的命令行之后,你会得到 app/directive/tooltip 的文件目录结构如下:

tooltip
├── tooltip                                           // tooltip 组件
│    ├── user-list.component.html                     // 页面骨架
│    ├── user-list.component.scss                     // 页面独有样式
│    ├── user-list.component.spec.ts                  // 测试文件
│    └── user-list.component.ts                       // javascript 文件
├── tooltip.directive.spec.ts                         // 测试文件
└── tooltip.directive.ts                              // 指令文件

嗯,这里我将组件放在 tooltip 的同级,主要是方便管理。当然,这个因人而异,你可以放在公共组件 components 文件夹内。

编写 tooltip 组件

html 文件中,有:

<div class="caret"></div>
<div class="tooltip-content">{{data.content}}</div>

在样式文件 .scss 中,有:

$black: #000000;
$white: #ffffff;
$caret-size: 6px;
$tooltip-bg: transparentize($black, 0.25); // transparentize 是 sass 的语法
$grid-gutter-width: 30px;
$body-bg-color: $white;
$app-anim-time: 200ms;
$app-anim-curve: ease-out;
$std-border-radius: 5px;
$zindex-max: 100;

// :host 伪类选择器,给组件元素本身设置样式
:host {
  position: fixed;
  padding: $grid-gutter-width/3 $grid-gutter-width/2;
  background-color: $tooltip-bg;
  color: $body-bg-color;
  opacity: 0;
  transition: all $app-anim-time $app-anim-curve;
  text-align: center;
  border-radius: $std-border-radius;
  z-index: $zindex-max;
}

.caret { // 脱字符
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 6px solid $tooltip-bg;
  position: absolute;
  top: -$caret-size;
  left: 50%;
  margin-left: -$caret-size/2;
  border-bottom-color: $tooltip-bg;
}

嗯~,css 是一个神奇的东西,之后会安排一篇文章来讲解下 sass 相关的内容...

然后,在 javascript 文件 tooltip.component.ts 内容如下:

import { 
  Component, 
  ElementRef, // 元素指向
  HostBinding, 
  OnDestroy, 
  OnInit 
} from '@angular/core';

@Component({
  selector: 'app-tooltip', // 标识符,表明我这个组件叫做啥,这里是 app-tooltip
  templateUrl: './tooltip.component.html', // 本组件的骨架
  styleUrls: ['./tooltip.component.scss'] // 本组件的私有样式
})
export class TooltipComponent implements OnInit {

  public data: any; // 在 directive 上赋值
  private displayTimeOut:any;

  // 组件本身 host 绑定相关的装饰器
  @HostBinding('style.top')  hostStyleTop!: string;
  @HostBinding('style.left') hostStyleLeft!: string;
  @HostBinding('style.opacity') hostStyleOpacity!: string;

  constructor(
    private elementRef: ElementRef
  ) { }

  ngOnInit(): void {
    this.hostStyleTop = this.data.elementPosition.bottom + 'px';

    if(this.displayTimeOut) {
      clearTimeout(this.displayTimeOut)
    }

    this.displayTimeOut = setTimeout((_: any) => {
      // 这里计算 tooltip 距离左侧的距离,这里计算公式是:tooltip.left + 目标元素的.width - (tooltip.width/2)
      this.hostStyleLeft = this.data.elementPosition.left + this.data.element.clientWidth / 2 - this.elementRef.nativeElement.clientWidth / 2 + 'px'
      this.hostStyleOpacity = '1';
      this.hostStyleTop = this.data.elementPosition.bottom + 10 + 'px'
    }, 500)
  }
  
  
  // 组件销毁
  ngOnDestroy() {
    // 组件销毁后,清除定时器,防止内存泄露
    if(this.displayTimeOut) {
      clearTimeout(this.displayTimeOut)
    }
  }
}

编写 tooltip 指令

这是本文的重点,具体的说明,我在代码上标注出来~

相关的文件 tooltip.directive.ts 内容如下:

import { 
  ApplicationRef, // 全局性调用检测
  ComponentFactoryResolver, // 创建组件对象
  ComponentRef, // 组件实例的关联和指引,指向 ComponentFactory 创建的元素
  Directive, ElementRef, 
  EmbeddedViewRef, // EmbeddedViewRef 继承于 ViewRef,用于表示模板元素中定义的 UI 元素。
  HostListener, // DOM 事件监听
  Injector, // 依赖注入
  Input 
} from '@angular/core';

import { TooltipComponent } from './tooltip/tooltip.component';

@Directive({
  selector: '[appTooltip]'
})
export class TooltipDirective {
  @Input("appTooltip") appTooltip!: string;

  private componentRef!: ComponentRef<TooltipComponent>;

  // 获取目标元素的相关位置,比如 left, right, top, bottom
  get elementPosition() {
    return this.elementRef.nativeElement.getBoundingClientRect(); 
  }

  constructor(
    protected elementRef: ElementRef,
    protected appRef: ApplicationRef,
    protected componentFactoryResolver: ComponentFactoryResolver,
    protected injector: Injector
  ) { }

  // 创建 tooltip
  protected createTooltip() {
    this.componentRef = this.componentFactoryResolver
      .resolveComponentFactory(TooltipComponent) // 绑定 tooltip 组件
      .create(this.injector);

    this.componentRef.instance.data = { // 绑定 data 数据
      content: this.appTooltip,
      element: this.elementRef.nativeElement,
      elementPosition: this.elementPosition
    }

    this.appRef.attachView(this.componentRef.hostView); // 添加视图
    const domElem = (this.componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
    document.body.appendChild(domElem);
  }
  
  // 删除 tooltip
  protected destroyTooltip() {
    if(this.componentRef) {
      this.appRef.detachView(this.componentRef.hostView); // 移除视图
      this.componentRef.destroy();
    }
  }
  
  // 监听鼠标移入
  @HostListener('mouseover')
  OnEnter() {
    this.createTooltip();
  }
    
  // 监听鼠标移出
  @HostListener('mouseout')
  OnOut() {
    this.destroyTooltip();
  }

}

到这里,已经完成了 99% 的功能了,下面我们在页面上调用即可。

页面上调用

我们在 user-list.component.html 上添加下面的内容:

<p style="margin-top: 100px;">
  <!-- [appTooltip]="'Hello Jimmy'" 是重点 -->
  <span 
    [appTooltip]="'Hello Jimmy'" 
    style="margin-left: 200px; width: 160px; text-align: center; padding: 20px 0; display: inline-block; border: 1px solid #999;"
  >Jimmy</span>
</p>

TooltipDirective 这个指令我们已经在 app.module.ts 上进行声明,我们直接调用即可。目前的效果如下:

效果-user-list.png

我们实现的 tooltip 是底部居中展示,也就是我们平常使用框架,比如 angular ant designtooltipbottom 属性。对于其他属性,读者感兴趣的话,可以进行扩展。

至此,我们可以很好的维护自己编写的指令文件了。

【完】✅

@reng99 reng99 added blog a single blog angular labels Jun 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
angular blog a single blog
Projects
None yet
Development

No branches or pull requests

1 participant