Định vị vị trí cho Tooltip Directive sử dụng Popper.js Core

Định vị vị trí cho Tooltip Directive sử dụng Popper.js Core

{tocify} $title={Table of Content}

Vấn đề

Ở bài trước chúng ta đã tạo thành công một Custom Attribute Directive - Tooltip. Nếu bạn chưa xem, hãy xem tại đây.

Tuy nhiên, khi bạn test với nhiều trường hợp khác nhau, bạn sẽ thấy rằng, đôi lúc tooltip sẽ hiện thị không đúng vị trí và nó không lật sang ví trị thích hợp khi không đủ không gian hiển thị:

incorrect position
Để giải quyết vấn đề này và cũng đồng thời cải thiện hiệu suất cho tooltip chúng ta hoạt động hiệu quả hơn. Chúng ta sẽ sử dụng thư viện Popper.js, đây là một thư viện nhẹ ~ 3 kB nhằm mục đích cung cấp một công cụ định vị đáng tin cậy và có thể mở rộng mà bạn có thể sử dụng để đảm bảo tất cả các phần tử popper (tooltip, popover) của bạn được định vị ở đúng vị trí.

Cài đặt Popper.js

Popper được sử dụng trong các thư viện phổ biến như Bootstrap, Foundation, Material UI,.. Nếu bạn đã cài Bootstrap thì bạn có thể không cần phải cài lại Popper nữa, nhưng nếu chưa thì bạn có thể dùng lệnh sau:
npm i @popperjs/core
//or
yarn add @popperjs/core

Bạn có thể xem thêm tại trang chủ của Popper.

Sử dụng Popper cho Tooltip Directive của chúng ta

Chúng ta sẽ chỉnh sửa lại một vài code của bài trước như sau:

  • Import các module cần thiết từ @popperjs/core:

tooltip.directive.tsimport { createPopper, Placement } from '@popperjs/core';

  • Thay đổi type input của biến placement:

tooltip.directive.ts@Input()
  placement: Placement = 'top';

Viết function setPositionByPopper để thay thế cho function setPosition của bài trước:

tooltip.directive.tssetPositionByPopper() {
    createPopper(this.el.nativeElement, this.tooltip, {
      placement: this.placement,
      modifiers: [
        {
          name: 'offset',
          options: {
            offset: [0, this.offset],
          },
        },
      ],
    });
  }

Bây giờ chúng ta sẽ chỉnh lại CSS cho phần mũi tên của tooltip:

styles.scss.ng-tooltip[data-popper-placement^="top"]:after {
  top: 100%;
  left: 50%;
  margin-left: -5px;
  border-width: 5px;
  border-color: black transparent transparent transparent;
}
.ng-tooltip[data-popper-placement^="bottom"]:after {
  bottom: 100%;
  left: 50%;
  margin-left: -5px;
  border-width: 5px;
  border-color: transparent transparent black transparent;
}
.ng-tooltip[data-popper-placement^="left"]:after {
  top: 50%;
  left: 100%;
  margin-top: -5px;
  border-width: 5px;
  border-color: transparent transparent transparent black;
}
.ng-tooltip[data-popper-placement^="right"]:after {
  top: 50%;
  right: 100%;
  margin-top: -5px;
  border-width: 5px;
  border-color: transparent black transparent transparent;
}

Code hoàn chỉnh của chúng ta như sau:

styles.scss.ng-tooltip[data-popper-placement^="top"]:after {
  top: 100%;
  left: 50%;
  margin-left: -5px;
  border-width: 5px;
  border-color: black transparent transparent transparent;
}
.ng-tooltip[data-popper-placement^="bottom"]:after {
  bottom: 100%;
  left: 50%;
  margin-left: -5px;
  border-width: 5px;
  border-color: transparent transparent black transparent;
}
.ng-tooltip[data-popper-placement^="left"]:after {
  top: 50%;
  left: 100%;
  margin-top: -5px;
  border-width: 5px;
  border-color: transparent transparent transparent black;
}
.ng-tooltip[data-popper-placement^="right"]:after {
  top: 50%;
  right: 100%;
  margin-top: -5px;
  border-width: 5px;
  border-color: transparent black transparent transparent;
}
tooltip.directive.tsimport {
  Directive,
  Input,
  ElementRef,
  HostListener,
  Renderer2,
  SecurityContext,
} from '@angular/core';
import { DomSanitizer } from "@angular/platform-browser";
import { createPopper, Placement } from '@popperjs/core';

@Directive({
  selector: '[tooltip]'
})
export class TooltipDirective {
  @Input('tooltip')
  tooltipTitle!: string; // tooltipTitle may be string or HTML element
  @Input()
  placement: Placement = 'top';
  @Input()
  delay: string | any;
  tooltip: HTMLElement | any;
  // Distance between host element and tooltip element
  offset = 10;
  // Allow HTML in the tooltip.
  @Input() htmlTooltip: boolean = false;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    private sanitizer: DomSanitizer
  ) { }

  @HostListener('mouseenter') onMouseEnter() {
    if (!this.tooltip) { this.show(); }
  }

  @HostListener('mouseleave') onMouseLeave() {
    if (this.tooltip) { this.hide(); }
  }

  show() {
    this.create();
    this.setPositionByPopper();
    this.renderer.addClass(this.tooltip, 'ng-tooltip-show');
  }

  hide() {
    this.renderer.removeClass(this.tooltip, 'ng-tooltip-show');
    window.setTimeout(() => {
      this.renderer.removeChild(document.body, this.tooltip);
      this.tooltip = null;
    }, this.delay);
  }

  create() {
    this.tooltip = this.renderer.createElement('span');

    if (this.htmlTooltip) {
      this.renderer.setProperty(
        this.tooltip,
        'innerHTML',
        this.sanitizer.sanitize(SecurityContext.HTML, this.tooltipTitle)
      );
    } else {
      this.renderer.appendChild(
        this.tooltip,
        this.renderer.createText(this.tooltipTitle) // textNode
      );
    }

    this.renderer.appendChild(document.body, this.tooltip);
    // this.renderer.appendChild(this.el.nativeElement, this.tooltip);

    this.renderer.addClass(this.tooltip, 'ng-tooltip');
    // this.renderer.addClass(this.tooltip, `ng-tooltip-${this.placement}`);

    // delay setting
    this.renderer.setStyle(this.tooltip, '-webkit-transition', `opacity ${this.delay}ms`);
    this.renderer.setStyle(this.tooltip, '-moz-transition', `opacity ${this.delay}ms`);
    this.renderer.setStyle(this.tooltip, '-o-transition', `opacity ${this.delay}ms`);
    this.renderer.setStyle(this.tooltip, 'transition', `opacity ${this.delay}ms`);
  }

  setPositionByPopper() {
    createPopper(this.el.nativeElement, this.tooltip, {
      placement: this.placement,
      modifiers: [
        {
          name: 'offset',
          options: {
            offset: [0, this.offset],
          },
        },
      ],
    });
  }
}

Source Code: https://github.com/QuocDung0209/custom-directive-angular/tree/tooltip-directive-v1.0

Demo: https://angulartutorials.netlify.app/tooltip-directive

Read more

Đăng nhận xét

Mới hơn Cũ hơn