{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ị:
Để 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
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