import { Component, Input, TemplateRef, ElementRef, OnInit, ViewChild, ViewContainerRef, HostBinding, ViewEncapsulation, SimpleChanges, Renderer2, NgZone } from '@angular/core'; import { AfterViewInit } from '@angular/core'; @Component({ selector: 'farris-tooltip', templateUrl: './tooltip.component.html', styleUrls: ['./tooltip.component.scss'], encapsulation: ViewEncapsulation.None }) export class TooltipComponent implements OnInit, AfterViewInit { @ViewChild('contentViewf', { read: ViewContainerRef }) contentViewf: ViewContainerRef; @ViewChild('tooltipEle', { read: ElementRef }) tooltipNode: ElementRef; @ViewChild('arrowEle', { read: ElementRef }) arrowNode: ElementRef; @ViewChild('innerEle', { read: ElementRef }) innerNode: ElementRef; contentIsString = true; /* 普通文本内容 */ @Input() set content(value: string | TemplateRef) { if (value instanceof TemplateRef) { this.contentIsString = false; this.contentViewf.createEmbeddedView(value); } else { this.contentIsString = true; this.nfContent = value; } } get content() { return this.nfContent; } private _tipWidth = ''; /**因为表单生成没有排除factory,不能使用模板 */ @Input() set tipWidth(value) { if (value != this._tipWidth) { if (value) { this.render.setStyle(this.innerNode.nativeElement, 'width', value); this.render.setStyle(this.innerNode.nativeElement, 'max-width', 'none'); } else { this.render.removeStyle(this.innerNode.nativeElement, 'width'); this.render.removeStyle(this.innerNode.nativeElement, 'max-width'); } this._tipWidth = value; } } get tipWidth() { return this._tipWidth; } private _tipCls = ''; @Input() set tipCls(value) { if (value != this._tipCls) { //移除旧样式 this._tipCls && this.render.removeClass(this.el.nativeElement, this._tipCls); //追加新样式 value && this.render.addClass(this.el.nativeElement, value); this._tipCls = value; } } get tipCls() { return this._tipCls; } /* 位置 */ @Input() placement = 'top'; private rectifyPlacement = 'top'; // 箭头位置参照 hostBoundingClientRect: any; // 边界位置参照 referenceBoundingRect: any; private rectifyGutter = 20; nfContent: string | TemplateRef; /* 位置样式 */ positionStyle = {}; constructor( public el: ElementRef, private render: Renderer2 ) { } ngOnInit() { } ngAfterViewInit() { if (this.hostBoundingClientRect) { this.judgeWidth(); this.autoRectifyDirection(); this.calculatePosition(this.hostBoundingClientRect); } } /** * 规则判断默认样式下出现滚动条,尺寸从默认变成大号 */ judgeWidth() { if (this.innerNode.nativeElement.scrollHeight > this.innerNode.nativeElement.clientHeight) { this.render.addClass(this.innerNode.nativeElement, 'tooltip-inner-lg'); } } // 自动纠正方向 private autoRectifyDirection() { // 标记默认值 this.rectifyPlacement = this.placement; // 整个提示位置 const tooltipSize = this.tooltipNode.nativeElement.getBoundingClientRect(); const arrowSize = this.arrowNode.nativeElement.getBoundingClientRect(); /*不考虑,提示框太大的情况,只考虑一次转换位置, 太复杂的场景,实际项目中不会出现,简化处理此问题 if(this.referenceBoundingRect.width>tooltipSize){ }*/ switch (this.placement) { case 'top': case 'top-left': case 'top-right': if (Math.abs(this.hostBoundingClientRect.top - this.referenceBoundingRect.top) < tooltipSize.height + arrowSize.height) { // 高度放不开,转向 this.rectifyPlacement = this.rectifyPlacement.replace('top', 'bottom'); } break; case 'bottom': case 'bottom-left': case 'bottom-right': if (Math.abs(this.hostBoundingClientRect.bottom - this.referenceBoundingRect.bottom) < tooltipSize.height + arrowSize.height) { // 高度放不开,转向 this.rectifyPlacement = this.rectifyPlacement.replace('bottom', 'top'); } break; case 'right': case 'right-top': case 'right-bottom': if (Math.abs(this.hostBoundingClientRect.right - this.referenceBoundingRect.right) < tooltipSize.width + arrowSize.width) { // 宽度放不开,转向 this.rectifyPlacement = this.rectifyPlacement.replace('right', 'left'); } break; case 'left': case 'left-top': case 'left-bottom': if (Math.abs(this.hostBoundingClientRect.left - this.referenceBoundingRect.left) < tooltipSize.width + arrowSize.width) { // 宽度放不开,转向 this.rectifyPlacement = this.rectifyPlacement.replace('left', 'right'); } break; } if (this.rectifyPlacement != this.placement) { this.tooltipNode.nativeElement.classList.remove('bs-tooltip-' + this.offsetDirection(this.placement)); this.tooltipNode.nativeElement.classList.add('bs-tooltip-' + this.offsetDirection(this.rectifyPlacement)); } } /** * 判断是否超出边界 */ private isOverBounding(rect, value) { let isOver = false; let newValue = 0; switch (rect) { case 'left': if (value <= this.referenceBoundingRect.left) { isOver = true; newValue = this.referenceBoundingRect.left + this.rectifyGutter; } break; case 'right': if (value >= this.referenceBoundingRect.right) { isOver = true; newValue = this.referenceBoundingRect.right - this.rectifyGutter; } break; case 'top': if (value <= this.referenceBoundingRect.top) { isOver = true; newValue = this.referenceBoundingRect.top + this.rectifyGutter; } break; case 'bottom': if (value >= this.referenceBoundingRect.bottom) { isOver = true; newValue = this.referenceBoundingRect.bottom - this.rectifyGutter; } break; } return { isOver, newValue }; } private containsEl(datas, specialEl = '') { if (!specialEl) { specialEl = this.rectifyPlacement; } for (var i = 0; i < datas.length; i++) { if (datas[i] == specialEl) return true; } return false; } /* 计算tooltip最新位置 */ calculatePosition(hostPosition: any) { let top = 0; let left = 0; let right = 0; let bottom = 0; // 整个提示位置 const tooltipSize = this.tooltipNode.nativeElement.getBoundingClientRect(); const arrowSize = this.arrowNode.nativeElement.getBoundingClientRect(); switch (this.rectifyPlacement) { case 'top': top = hostPosition.top - tooltipSize.height - 2; left = hostPosition.left + (hostPosition.width - tooltipSize.width) / 2; right = left + tooltipSize.width; break; case 'left': top = hostPosition.top + (hostPosition.height - tooltipSize.height) / 2; left = hostPosition.left - tooltipSize.width - 2; break; case 'right': top = hostPosition.top + (hostPosition.height - tooltipSize.height) / 2; left = hostPosition.right + 2; break; case 'bottom': top = hostPosition.bottom + arrowSize.height + 2; left = hostPosition.left + (hostPosition.width - tooltipSize.width) / 2; right = left + tooltipSize.width; break; case 'top-left': top = hostPosition.top - tooltipSize.height - 2; left = hostPosition.left; right = left + tooltipSize.width; break; case 'top-right': top = hostPosition.top - tooltipSize.height - 2; left = hostPosition.right - tooltipSize.width; right = hostPosition.right; break; case 'right-top': top = hostPosition.top; left = hostPosition.right + 2; this.arrowNode.nativeElement.style.top = '10%'; break; case 'right-bottom': top = hostPosition.bottom - tooltipSize.height; left = hostPosition.right + 2; this.arrowNode.nativeElement.style.bottom = '10%'; break; case 'bottom-left': top = hostPosition.bottom + arrowSize.height + 2; left = hostPosition.left; right = left + tooltipSize.width; break; case 'bottom-right': top = hostPosition.bottom + arrowSize.height + 2; left = hostPosition.right - tooltipSize.width; right = hostPosition.right; break; case 'left-top': top = hostPosition.top; left = hostPosition.left - tooltipSize.width - 2; this.arrowNode.nativeElement.style.top = '10%'; break; case 'left-bottom': top = hostPosition.bottom - tooltipSize.height; left = hostPosition.left - tooltipSize.width - 2; this.arrowNode.nativeElement.style.bottom = '10%'; } let overResult; let arrowLeft = 0; let arrowTop = 0 switch (this.rectifyPlacement) { case 'top': case 'top-left': case 'top-right': case 'bottom': case 'bottom-left': case 'bottom-right': overResult = this.isOverBounding('left', left); if (overResult.isOver) { left = overResult.newValue; } else { overResult = this.isOverBounding('right', right); if (overResult.isOver) { left = overResult.newValue - tooltipSize.width; } } arrowLeft = left - hostPosition.left - hostPosition.width * 0.5 + arrowSize.width * 0.5; if (this.rectifyPlacement.indexOf('-left') > 0) { arrowLeft += hostPosition.width * 0.4; } else if (this.rectifyPlacement.indexOf('-right') > 0) { arrowLeft -= hostPosition.width * 0.4; } this.arrowNode.nativeElement.style.left = Math.abs(arrowLeft) + 'px'; break; default: overResult = this.isOverBounding('top', top); if (overResult.isOver) { top = overResult.newValue; } else { overResult = this.isOverBounding('bottom', bottom); if (overResult.isOver) { top = overResult.newValue - tooltipSize.height; } } arrowTop = top - hostPosition.top - hostPosition.height * 0.5 + arrowSize.height * 0.5;; if (this.rectifyPlacement.indexOf('-top') > 0) { arrowTop += hostPosition.height * 0.4; } else if (this.rectifyPlacement.indexOf('-bottom') > 0) { arrowTop -= hostPosition.height * 0.4; } this.arrowNode.nativeElement.style.top = Math.abs(arrowTop) + 'px'; } this.tooltipNode.nativeElement.style.top = top + 'px'; this.tooltipNode.nativeElement.style.left = left + 'px'; } /* *12个位置名解析 * 因表单生成没有排除factory,而且使用了[class],只能在此处变更 */ offsetDirection(tplacement = '') { if (!tplacement) { tplacement = this.placement; } if (tplacement.indexOf('-') !== -1) { const index = tplacement.indexOf('-'); return tplacement.substring(0, index); } return tplacement; } }