Angular自定义浮层提示面板
有时候我们需要在指定的组件旁边显示一个浮层提示面板,在里面可以自定义内容。如下图所示

overlaypanel.component.ts
tsimport { animate, state, style, transition, trigger } from "@angular/animations";
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, ElementRef, EventEmitter, Inject, Input, NgZone, Output, QueryList, Renderer2, TemplateRef, ViewEncapsulation } from "@angular/core";
import {TlTemplateDirective} from "../../common"
import { DomHandler } from './domhandler';
import { ConnectedOverlayScrollHandler } from "../../common/connectedoverlayscrollhandler";
@Component({
selector: 'cus-overlay-panel',
templateUrl: "./overlaypanel.component.html",
animations: [
trigger('animation', [
state('void', style({
transform: 'scaleY(0.8)',
opacity: 0
})),
state('close', style({
opacity: 0
})),
state('open', style({
transform: 'translateY(0)',
opacity: 1
})),
transition('void => open', animate('{{showTransitionParams}}')),
transition('open => close', animate('{{hideTransitionParams}}')),
])
],
host: {
class: 'cus-overlay-panel',
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class CusOverlayPanelComponent{
overlayVisible = false
render = false
isContainerClicked = true
documentClickListener = null
documentResizeListener = null
scrollHandler = null
container = null
target = null
destroyCallback = null
filled = false
// 设置点击弹层之外区域隐藏弹窗
@Input()dismissable:boolean = true
// 设置显示弹层右上方关闭小图标,点击图标关闭弹窗
@Input()showCloseIcon:boolean = false
@Input() style: any = null;
@Input() styleClass: string = "";
// 浮层弹窗添加到哪个父容器 [appendTo]="'body'"
@Input() appendTo: any = null;
// 设置弹窗面板层级自动分配
@Input() autoZIndex: boolean = true;
@Input() baseZIndex: number = 0;
@Input() ariaCloseLabel = null
@Input() focusOnShow = true
@Input() showTransitionOptions:string = ".12s cubic-bezier(0, 0, 0.2, 1)"
@Input() hideTransitionOptions:string = ".1s linear"
@Output() readonly onShow = new EventEmitter();
@Output() readonly onHide = new EventEmitter();
@ContentChildren(TlTemplateDirective)
templates?: QueryList<TlTemplateDirective>
contentTemplate?: TemplateRef<void> | null = null
constructor(public el: ElementRef, public renderer: Renderer2, @Inject(ChangeDetectorRef) public readonly cd: ChangeDetectorRef,public zone: NgZone) {
//this.el = el.nativeElement;
}
ngAfterContentInit() {
this.templates.forEach((item) => {
switch (item.getType()) {
case 'content':
this.contentTemplate = item.template;
break;
default:
this.contentTemplate = item.template;
break;
}
this.cd.markForCheck();
});
console.log(this.contentTemplate)
}
onContainerClick() {
debugger
this.isContainerClicked = true;
}
bindDocumentClickListener() {
if (!this.documentClickListener && this.dismissable) {
this.zone.runOutsideAngular(() => {
let documentEvent = DomHandler.isIOS() ? 'touchstart' : 'click';
const documentTarget = this.el ? this.el.nativeElement.ownerDocument : 'document';
this.documentClickListener = this.renderer.listen(documentTarget, documentEvent, (event) => {
if (!this.container.contains(event.target) && this.target !== event.target && !this.target.contains(event.target) && !this.isContainerClicked) {
this.zone.run(() => {
this.hide();
});
}
this.isContainerClicked = false;
this.cd.markForCheck();
});
});
}
}
unbindDocumentClickListener() {
if (this.documentClickListener) {
this.documentClickListener();
this.documentClickListener = null;
}
}
toggle(event, target) {
debugger
console.log("toggle")
if (this.overlayVisible) {
console.log("hasTargetChanged: ",this.hasTargetChanged(event, target))
if (this.hasTargetChanged(event, target)) {
this.destroyCallback = () => {
debugger
this.show(null, (target || event.currentTarget || event.target));
};
}
this.hide();
}
else {
this.show(event, target);
}
}
show(event, target) {
debugger
this.target = target || event.currentTarget || event.target;
this.overlayVisible = true;
this.render = true;
this.cd.markForCheck();
console.log("render: ",this.render)
}
hasTargetChanged(event, target) {
return this.target != null && this.target !== (target || event.currentTarget || event.target);
}
appendContainer() {
if (this.appendTo) {
if (this.appendTo === 'body')
document.body.appendChild(this.container);
else
DomHandler.appendChild(this.container, this.appendTo);
}
}
restoreAppend() {
if (this.container && this.appendTo) {
this.el.nativeElement.appendChild(this.container);
}
}
align() {
debugger
if (this.autoZIndex) {
this.container.style.zIndex = String(this.baseZIndex + (++DomHandler.zindex));
}
DomHandler.absolutePosition(this.container, this.target);
if (DomHandler.getOffset(this.container).top < DomHandler.getOffset(this.target).top) {
DomHandler.addClass(this.container, 'ui-overlaypanel-flipped');
}
if (Math.floor(DomHandler.getOffset(this.container).left) < Math.floor(DomHandler.getOffset(this.target).left) &&
DomHandler.getOffset(this.container).left > 0) {
DomHandler.addClass(this.container, 'ui-overlaypanel-shifted');
}
}
onAnimationStart(event) {
debugger
if (event.toState === 'open') {
this.container = event.element;
this.onShow.emit(null);
this.appendContainer();
this.align();
this.bindDocumentClickListener();
this.bindDocumentResizeListener();
this.bindScrollListener();
if (this.focusOnShow) {
this.focus();
}
}
}
onAnimationEnd(event) {
debugger
switch (event.toState) {
case 'void':
if (this.destroyCallback) {
this.destroyCallback();
this.destroyCallback = null;
}
break;
case 'close':
this.onContainerDestroy();
this.onHide.emit({});
this.render = false;
console.log("render: ",this.render)
break;
}
}
focus() {
let focusable = DomHandler.findSingle(this.container, '[autofocus]');
if (focusable) {
this.zone.runOutsideAngular(() => {
setTimeout(() => focusable.focus(), 5);
});
}
}
hide() {
debugger
this.overlayVisible = false;
this.cd.markForCheck();
}
onCloseClick(event) {
this.hide();
event.preventDefault();
}
onWindowResize(event) {
this.hide();
}
bindDocumentResizeListener() {
this.documentResizeListener = this.onWindowResize.bind(this);
window.addEventListener('resize', this.documentResizeListener);
}
unbindDocumentResizeListener() {
if (this.documentResizeListener) {
window.removeEventListener('resize', this.documentResizeListener);
this.documentResizeListener = null;
}
}
bindScrollListener() {
if (!this.scrollHandler) {
this.scrollHandler = new ConnectedOverlayScrollHandler(this.target, () => {
if (this.overlayVisible) {
this.hide();
}
});
}
this.scrollHandler.bindScrollListener();
}
unbindScrollListener() {
if (this.scrollHandler) {
this.scrollHandler.unbindScrollListener();
}
}
onContainerDestroy() {
this.target = null;
this.unbindDocumentClickListener();
this.unbindDocumentResizeListener();
this.unbindScrollListener();
}
ngOnDestroy() {
if (this.scrollHandler) {
this.scrollHandler.destroy();
this.scrollHandler = null;
}
this.target = null;
this.destroyCallback = null;
if (this.container) {
this.restoreAppend();
this.onContainerDestroy();
}
}
}
overlaypanel.component.html
html<div *ngIf="render" [ngClass]="'ui-overlaypanel ui-component'" [ngStyle]="style" [class]="styleClass"
(click)="onContainerClick()"
[@animation]="{value: (overlayVisible ? 'open': 'close'), params: {showTransitionParams: showTransitionOptions, hideTransitionParams: hideTransitionOptions}}"
(@animation.start)="onAnimationStart($event)" (@animation.done)="onAnimationEnd($event)">
<div class="ui-overlaypanel-content">
<ng-content></ng-content>
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
<button *ngIf="showCloseIcon" type="button" class="ui-overlaypanel-close ui-link" (click)="onCloseClick($event)"
(keydown.enter)="hide()" [attr.aria-label]="ariaCloseLabel" tRipple>
<span class="ui-overlaypanel-close-icon icon-smallcolse"></span>
</button>
</div>
ConnectedOverlayScrollHandler.ts
tsimport { DomHandler } from './domhandler';
export class ConnectedOverlayScrollHandler {
element = null
listener = null
scrollableParents = null
constructor(element, listener = () => { }) {
this.element = element;
this.listener = listener;
}
bindScrollListener() {
this.scrollableParents = DomHandler.getScrollableParents(this.element);
for (let i = 0; i < this.scrollableParents.length; i++) {
this.scrollableParents[i].addEventListener('scroll', this.listener);
}
}
unbindScrollListener() {
if (this.scrollableParents) {
for (let i = 0; i < this.scrollableParents.length; i++) {
this.scrollableParents[i].removeEventListener('scroll', this.listener);
}
}
}
destroy() {
this.unbindScrollListener();
this.element = null;
this.listener = null;
this.scrollableParents = null;
}
}
domhandler.ts
tsexport class DomHandler {
static zindex = 1000
static calculatedScrollbarWidth = null
static calculatedScrollbarHeight = null
static browser = null
static addClass(element, className) {
if (element.classList)
element.classList.add(className);
else
element.className += ' ' + className;
}
static addMultipleClasses(element, className) {
if (element.classList) {
let styles = className.trim().split(' ');
for (let i = 0; i < styles.length; i++) {
element.classList.add(styles[i]);
}
}
else {
let styles = className.split(' ');
for (let i = 0; i < styles.length; i++) {
element.className += ' ' + styles[i];
}
}
}
static removeClass(element, className) {
if (element.classList)
element.classList.remove(className);
else
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
static hasClass(element, className) {
if (element.classList)
return element.classList.contains(className);
else
return new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className);
}
static siblings(element) {
return Array.prototype.filter.call(element.parentNode.children, function (child) {
return child !== element;
});
}
static find(element, selector) {
return Array.from(element.querySelectorAll(selector));
}
static findSingle(element, selector) {
if (element) {
return element.querySelector(selector);
}
return null;
}
static index(element) {
let children = element.parentNode.childNodes;
let num = 0;
for (let i = 0; i < children.length; i++) {
if (children[i] == element)
return num;
if (children[i].nodeType == 1)
num++;
}
return -1;
}
static indexWithinGroup(element, attributeName) {
let children = element.parentNode ? element.parentNode.childNodes : [];
let num = 0;
for (let i = 0; i < children.length; i++) {
if (children[i] == element)
return num;
if (children[i].attributes && children[i].attributes[attributeName] && children[i].nodeType == 1)
num++;
}
return -1;
}
static relativePosition(element, target) {
debugger
let elementDimensions:any = element.parentNode ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element);
const targetHeight = target.offsetHeight;
const targetOffset = target.getBoundingClientRect();
const viewport = this.getViewport();
let top, left;
if ((targetOffset.top + targetHeight + elementDimensions.height) > viewport.height) {
top = -1 * (elementDimensions.height);
element.style.transformOrigin = 'bottom';
if (targetOffset.top + top < 0) {
top = -1 * targetOffset.top;
}
}
else {
top = targetHeight;
element.style.transformOrigin = 'top';
}
if (elementDimensions.width > viewport.width) {
// element wider then viewport and cannot fit on screen (align at left side of viewport)
left = targetOffset.left * -1;
}
else if ((targetOffset.left + elementDimensions.width) > viewport.width) {
// element wider then viewport but can be fit on screen (align at right side of viewport)
left = (targetOffset.left + elementDimensions.width - viewport.width) * -1;
}
else {
// element fits on screen (align with target)
left = 0;
}
element.style.top = top + 'px';
element.style.left = left + 'px';
}
static absolutePosition(element, target) {
debugger
let elementDimensions:any = element.parentNode ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element);
let elementOuterHeight = elementDimensions.height;
let elementOuterWidth = elementDimensions.width;
let targetOuterHeight = target.offsetHeight;
let targetOuterWidth = target.offsetWidth;
let targetOffset = target.getBoundingClientRect();
let windowScrollTop = this.getWindowScrollTop();
let windowScrollLeft = this.getWindowScrollLeft();
let viewport = this.getViewport();
let top, left;
if (targetOffset.top + targetOuterHeight + elementOuterHeight > viewport.height) {
top = targetOffset.top + windowScrollTop - elementOuterHeight;
element.style.transformOrigin = 'bottom';
if (top < 0) {
top = windowScrollTop;
}
}
else {
top = targetOuterHeight + targetOffset.top + windowScrollTop;
element.style.transformOrigin = 'top';
}
if (targetOffset.left + elementOuterWidth > viewport.width)
left = Math.max(0, targetOffset.left + windowScrollLeft + targetOuterWidth - elementOuterWidth);
else
left = targetOffset.left + windowScrollLeft;
element.style.top = top + 'px';
element.style.left = left + 'px';
}
static getParents(element, parents = []) {
return element['parentNode'] === null ? parents : this.getParents(element.parentNode, parents.concat([element.parentNode]));
}
static getScrollableParents(element) {
let scrollableParents = [];
if (element) {
let parents = this.getParents(element);
const overflowRegex = /(auto|scroll)/;
const overflowCheck = (node) => {
let styleDeclaration = window['getComputedStyle'](node, null);
return overflowRegex.test(styleDeclaration.getPropertyValue('overflow')) || overflowRegex.test(styleDeclaration.getPropertyValue('overflowX')) || overflowRegex.test(styleDeclaration.getPropertyValue('overflowY'));
};
for (let parent of parents) {
let scrollSelectors = parent.nodeType === 1 && parent.dataset['scrollselectors'];
if (scrollSelectors) {
let selectors = scrollSelectors.split(',');
for (let selector of selectors) {
let el = this.findSingle(parent, selector);
if (el && overflowCheck(el)) {
scrollableParents.push(el);
}
}
}
if (parent.nodeType !== 9 && overflowCheck(parent)) {
scrollableParents.push(parent);
}
}
}
return scrollableParents;
}
static getHiddenElementOuterHeight(element) {
element.style.visibility = 'hidden';
element.style.display = 'block';
let elementHeight = element.offsetHeight;
element.style.display = 'none';
element.style.visibility = 'visible';
return elementHeight;
}
static getHiddenElementOuterWidth(element) {
element.style.visibility = 'hidden';
element.style.display = 'block';
let elementWidth = element.offsetWidth;
element.style.display = 'none';
element.style.visibility = 'visible';
return elementWidth;
}
static getHiddenElementDimensions(element) {
let dimensions:any = {};
element.style.visibility = 'hidden';
element.style.display = 'block';
dimensions.width = element.offsetWidth;
dimensions.height = element.offsetHeight;
element.style.display = 'none';
element.style.visibility = 'visible';
return dimensions;
}
static scrollInView(container, item) {
let borderTopValue = getComputedStyle(container).getPropertyValue('borderTopWidth');
let borderTop = borderTopValue ? parseFloat(borderTopValue) : 0;
let paddingTopValue = getComputedStyle(container).getPropertyValue('paddingTop');
let paddingTop = paddingTopValue ? parseFloat(paddingTopValue) : 0;
let containerRect = container.getBoundingClientRect();
let itemRect = item.getBoundingClientRect();
let offset = (itemRect.top + document.body.scrollTop) - (containerRect.top + document.body.scrollTop) - borderTop - paddingTop;
let scroll = container.scrollTop;
let elementHeight = container.clientHeight;
let itemHeight = this.getOuterHeight(item);
if (offset < 0) {
container.scrollTop = scroll + offset;
}
else if ((offset + itemHeight) > elementHeight) {
container.scrollTop = scroll + offset - elementHeight + itemHeight;
}
}
static fadeIn(element, duration) {
element.style.opacity = 0;
let last = +new Date();
let opacity = 0;
let tick = function () {
opacity = +element.style.opacity.replace(",", ".") + (new Date().getTime() - last) / duration;
element.style.opacity = opacity;
last = +new Date();
if (+opacity < 1) {
(window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16);
}
};
tick();
}
static fadeOut(element, ms) {
var opacity = 1, interval = 50, duration = ms, gap = interval / duration;
let fading = setInterval(() => {
opacity = opacity - gap;
if (opacity <= 0) {
opacity = 0;
clearInterval(fading);
}
element.style.opacity = opacity;
}, interval);
}
static getWindowScrollTop() {
let doc = document.documentElement;
return (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
}
static getWindowScrollLeft() {
let doc = document.documentElement;
return (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
}
static matches(element, selector) {
var p = Element.prototype;
var f = p['matches'] || p.webkitMatchesSelector || p['mozMatchesSelector'] || p['msMatchesSelector'] || function (s) {
return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
};
return f.call(element, selector);
}
static getOuterWidth(el, margin = null) {
let width = el.offsetWidth;
if (margin) {
let style = getComputedStyle(el);
width += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
}
return width;
}
static getHorizontalPadding(el) {
let style = getComputedStyle(el);
return parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
}
static getHorizontalMargin(el) {
let style = getComputedStyle(el);
return parseFloat(style.marginLeft) + parseFloat(style.marginRight);
}
static innerWidth(el) {
let width = el.offsetWidth;
let style = getComputedStyle(el);
width += parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
return width;
}
static width(el) {
let width = el.offsetWidth;
let style = getComputedStyle(el);
width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
return width;
}
static getInnerHeight(el) {
let height = el.offsetHeight;
let style = getComputedStyle(el);
height += parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
return height;
}
static getOuterHeight(el, margin = null) {
let height = el.offsetHeight;
if (margin) {
let style = getComputedStyle(el);
height += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
}
return height;
}
static getHeight(el) {
let height = el.offsetHeight;
let style = getComputedStyle(el);
height -= parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) + parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
return height;
}
static getWidth(el) {
let width = el.offsetWidth;
let style = getComputedStyle(el);
width -= parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
return width;
}
static getViewport() {
let win = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0], w = win.innerWidth || e.clientWidth || g.clientWidth, h = win.innerHeight || e.clientHeight || g.clientHeight;
return { width: w, height: h };
}
static getOffset(el) {
var rect = el.getBoundingClientRect();
return {
top: rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0),
left: rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0),
};
}
static replaceElementWith(element, replacementElement) {
let parentNode = element.parentNode;
if (!parentNode)
throw `Can't replace element`;
return parentNode.replaceChild(replacementElement, element);
}
static getUserAgent() {
return navigator.userAgent;
}
static isIE() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) {
// IE 10 or older => return version number
return true;
}
var trident = ua.indexOf('Trident/');
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return true;
}
var edge = ua.indexOf('Edge/');
if (edge > 0) {
// Edge (IE 12+) => return version number
return true;
}
// other browser
return false;
}
static isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window['MSStream'];
}
static isAndroid() {
return /(android)/i.test(navigator.userAgent);
}
static isTouchDevice() {
return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0));
}
static appendChild(element, target) {
if (this.isElement(target))
target.appendChild(element);
else if (target.el && target.el.nativeElement)
target.el.nativeElement.appendChild(element);
else
throw 'Cannot append ' + target + ' to ' + element;
}
static removeChild(element, target) {
if (this.isElement(target))
target.removeChild(element);
else if (target.el && target.el.nativeElement)
target.el.nativeElement.removeChild(element);
else
throw 'Cannot remove ' + element + ' from ' + target;
}
static removeElement(element) {
if (!('remove' in Element.prototype))
element.parentNode.removeChild(element);
else
element.remove();
}
static isElement(obj) {
return (typeof HTMLElement === "object" ? obj instanceof HTMLElement :
obj && typeof obj === "object" && obj !== null && obj.nodeType === 1 && typeof obj.nodeName === "string");
}
static calculateScrollbarWidth(el) {
if (el) {
//console.log(el,1)
let style = getComputedStyle(el);
return (el.offsetWidth - el.clientWidth - parseFloat(style.borderLeftWidth) - parseFloat(style.borderRightWidth));
}
else {
if (this.calculatedScrollbarWidth !== null)
return this.calculatedScrollbarWidth;
let scrollDiv = document.createElement("div");
scrollDiv.className = "ui-scrollbar-measure";
document.body.appendChild(scrollDiv);
//console.log(scrollDiv.offsetWidth, scrollDiv.clientWidth,2)
let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
document.body.removeChild(scrollDiv);
this.calculatedScrollbarWidth = scrollbarWidth;
return scrollbarWidth;
}
}
static calculateScrollbarHeight() {
if (this.calculatedScrollbarHeight !== null)
return this.calculatedScrollbarHeight;
let scrollDiv = document.createElement("div");
scrollDiv.className = "ui-scrollbar-measure";
document.body.appendChild(scrollDiv);
let scrollbarHeight = scrollDiv.offsetHeight - scrollDiv.clientHeight;
document.body.removeChild(scrollDiv);
this.calculatedScrollbarWidth = scrollbarHeight;
return scrollbarHeight;
}
static invokeElementMethod(element, methodName, args) {
element[methodName].apply(element, args);
}
static clearSelection() {
if (window.getSelection) {
if (window.getSelection().empty) {
window.getSelection().empty();
}
else if (window.getSelection().removeAllRanges && window.getSelection().rangeCount > 0 && window.getSelection().getRangeAt(0).getClientRects().length > 0) {
window.getSelection().removeAllRanges();
}
}
else if (document['selection'] && document['selection'].empty) {
try {
document['selection'].empty();
}
catch (error) {
//ignore IE bug
}
}
}
static getBrowser() {
if (!this.browser) {
let matched = this.resolveUserAgent();
this.browser = {};
if (matched.browser) {
this.browser[matched.browser] = true;
this.browser['version'] = matched.version;
}
if (this.browser['chrome']) {
this.browser['webkit'] = true;
}
else if (this.browser['webkit']) {
this.browser['safari'] = true;
}
}
return this.browser;
}
static resolveUserAgent() {
let ua = navigator.userAgent.toLowerCase();
let match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
return {
browser: match[1] || "",
version: match[2] || "0"
};
}
static isInteger(value) {
if (Number.isInteger) {
return Number.isInteger(value);
}
else {
return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
}
}
static isHidden(element) {
return element.offsetParent === null;
}
static getFocusableElements(element) {
let focusableElements:any = DomHandler.find(element, `button:not([tabindex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden]),
[href][clientHeight][clientWidth]:not([tabindex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden]),
input:not([tabindex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden]), select:not([tabindex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden]),
textarea:not([tabindex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden]), [tabIndex]:not([tabIndex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden]),
[contenteditable]:not([tabIndex = "-1"]):not([disabled]):not([style*="display:none"]):not([hidden])`);
let visibleFocusableElements = [];
for (let focusableElement of focusableElements) {
if (getComputedStyle(focusableElement).display != "none" && getComputedStyle(focusableElement).visibility != "hidden")
visibleFocusableElements.push(focusableElement);
}
return visibleFocusableElements;
}
static generateZIndex() {
this.zindex = this.zindex || 999;
return ++this.zindex;
}
}
TlTemplateDirective.ts
tsimport { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
@Directive({
selector: '[tlTemplate]'
})
export class TlTemplateDirective {
@Input('tlTemplate')
name: string = "default"
// @Input()
// type: string = ""
constructor(private viewContainer: ViewContainerRef, public template: TemplateRef<any>) {
//this.template = templateRef;
}
ngOnInit(): void {
this.viewContainer.createEmbeddedView(this.template)
}
getType() {
return this.name;
}
}
ripple.ts
import { Directive, ElementRef, HostListener, NgZone } from '@angular/core'; import { NgControl } from '@angular/forms'; import { DomHandler } from '../../common/domhandler'; @Directive({ selector: '[tRipple]', host: { '[class.ui-ripple]': 'true' } }) export class Ripple { config = { ripple: false } mouseDownListener = null animationListener = null constructor(public el:ElementRef, public zone:NgZone, ) { this.el = el; this.zone = zone; //this.config = config; } ngAfterViewInit() { if (this.config && this.config.ripple) { this.zone.runOutsideAngular(() => { this.create(); this.mouseDownListener = this.onMouseDown.bind(this); this.el.nativeElement.addEventListener('mousedown', this.mouseDownListener); }); } } onMouseDown(event) { let ink = this.getInk(); if (!ink || getComputedStyle(ink, null).display === 'none') { return; } DomHandler.removeClass(ink, 'ui-ink-active'); if (!DomHandler.getHeight(ink) && !DomHandler.getWidth(ink)) { let d = Math.max(DomHandler.getOuterWidth(this.el.nativeElement), DomHandler.getOuterHeight(this.el.nativeElement)); ink.style.height = d + 'px'; ink.style.width = d + 'px'; } let offset = DomHandler.getOffset(this.el.nativeElement); let x = event.pageX - offset.left + document.body.scrollTop - DomHandler.getWidth(ink) / 2; let y = event.pageY - offset.top + document.body.scrollLeft - DomHandler.getHeight(ink) / 2; ink.style.top = y + 'px'; ink.style.left = x + 'px'; DomHandler.addClass(ink, 'ui-ink-active'); } getInk() { for (let i = 0; i < this.el.nativeElement.children.length; i++) { if (this.el.nativeElement.children[i].className.indexOf('ui-ink') !== -1) { return this.el.nativeElement.children[i]; } } return null; } resetInk() { let ink = this.getInk(); if (ink) { DomHandler.removeClass(ink, 'ui-ink-active'); } } onAnimationEnd(event) { DomHandler.removeClass(event.currentTarget, 'ui-ink-active'); } create() { let ink = document.createElement('span'); ink.className = 'ui-ink'; this.el.nativeElement.appendChild(ink); this.animationListener = this.onAnimationEnd.bind(this); ink.addEventListener('animationend', this.animationListener); } remove() { let ink = this.getInk(); if (ink) { this.el.nativeElement.removeEventListener('mousedown', this.mouseDownListener); ink.removeEventListener('animationend', this.animationListener); DomHandler.removeElement(ink); } } ngOnDestroy() { if (this.config && this.config.ripple) { this.remove(); } } }
使用案例
<a class="buttn-a" tButton (click)="op.toggle($event)">点击按钮</a> <cus-overlay-panel #op [appendTo]="'body'"> <img src="./assets/images/demo/car/Audi.png" alt="Galleria 1" /> </cus-overlay-panel>


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!