UNPKG

ng-devui

Version:

DevUI components based on Angular

264 lines (259 loc) 12.8 kB
import * as i0 from '@angular/core'; import { EventEmitter, Component, HostBinding, Input, Output, ViewChild, HostListener, NgModule } from '@angular/core'; import * as i1 from 'ng-devui/window-ref'; import { WindowRefModule } from 'ng-devui/window-ref'; import { fromEvent } from 'rxjs'; import { throttleTime, filter } from 'rxjs/operators'; class StickyComponent { set status(status) { if (status !== this._status) { this._prevStatus = this._status; this._status = status; this.statusChange.emit(this._status); this.statusProcess(this._status); } } get status() { return this._status; } constructor(el, windowRef) { this.el = el; this.windowRef = windowRef; this.hostPosition = 'relative'; this.statusChange = new EventEmitter(); this._prevStatus = undefined; this._status = 'normal'; this.THROTTLE_DELAY = 16; this.THROTTLE_TRIGGER = 100; this.throttle = () => { const fn = this.scrollAndResizeHock; const time = Date.now(); if (this.scrollTimer) { clearTimeout(this.scrollTimer); } if (!this.scrollPreStart) { this.scrollPreStart = time; } if (time - this.scrollPreStart > this.THROTTLE_TRIGGER) { fn(); this.scrollPreStart = null; this.scrollTimer = null; } else { this.scrollTimer = setTimeout(() => { fn(); this.scrollPreStart = null; this.scrollTimer = null; }, this.THROTTLE_DELAY); } }; this.scrollAndResizeHock = () => { if (this.container.getBoundingClientRect().left - (this.containerLeft || 0) !== 0) { this.status = 'stay'; this.containerLeft = this.container.getBoundingClientRect().left; } else { this.scrollHandler(); } }; this.scrollHandler = () => { const viewOffsetTop = this.scrollTarget && this.scrollTarget !== this.windowRef.window ? this.scrollTarget.getBoundingClientRect().top : 0; const computedStyle = this.windowRef.window.getComputedStyle(this.container); if (this.parentNode.getBoundingClientRect().top - viewOffsetTop > ((this.view && this.view.top) || 0)) { this.status = 'normal'; // 全局滑动(container!==parentNode)时候增加预判 } else if (this.container.getBoundingClientRect().top + parseInt(computedStyle.paddingTop, 10) + parseInt(computedStyle.borderTopWidth, 10) - viewOffsetTop >= ((this.view && this.view.top) || 0)) { this.status = 'normal'; } else if (this.container.getBoundingClientRect().bottom - parseInt(computedStyle.paddingBottom, 10) - parseInt(computedStyle.borderBottomWidth, 10) < viewOffsetTop + ((this.view && this.view.top) || 0) + this.wrapper.nativeElement.getBoundingClientRect().height + ((this.view && this.view.bottom) || 0)) { this.status = 'remain'; } else if (this.container.getBoundingClientRect().top + parseInt(computedStyle.paddingTop, 10) - viewOffsetTop < ((this.view && this.view.top) || 0)) { this.status = 'follow'; } }; } ngOnInit() { this.parentNode = this.el.nativeElement.parentNode; if (!this.container) { this.container = this.parentNode; } } ngAfterViewInit() { this.scrollTarget = this.scrollTarget || this.windowRef.window; // window有scroll事件,document.documentElement没有scroll事件 this.scrollTarget.addEventListener('scroll', this.throttle); this.initScrollStatus(this.scrollTarget); if (this.scrollTarget !== this.windowRef.window) { this.subscription = fromEvent(this.windowRef.window, 'scroll') .pipe(throttleTime(100, undefined, { leading: true, trailing: true }), filter((event) => event.target !== this.scrollTarget && (event.target === this.windowRef.window || event.target === this.windowRef.document || // fix ie11 document.contains is not defined (event.target.contains && event.target.contains(this.scrollTarget))))) .subscribe((event) => { this.statusProcess(this._status); }); } } ngOnDestroy() { this.scrollTarget.removeEventListener('scroll', this.throttle); if (this.subscription) { this.subscription.unsubscribe(); } } statusProcess(status) { switch (status) { case 'normal': this.wrapper.nativeElement.style.top = 'auto'; this.wrapper.nativeElement.style.left = 'auto'; this.wrapper.nativeElement.style.position = 'static'; break; case 'follow': { const viewOffset = this.scrollTarget && this.scrollTarget !== this.windowRef.window ? this.scrollTarget.getBoundingClientRect().top : 0; this.wrapper.nativeElement.style.top = Number(viewOffset) + ((this.view && this.view.top) || 0) + 'px'; this.wrapper.nativeElement.style.left = this.wrapper.nativeElement.getBoundingClientRect().left + 'px'; this.wrapper.nativeElement.style.position = 'fixed'; break; } case 'stay': { this.wrapper.nativeElement.style.top = this.calculateRelativePosition(this.wrapper.nativeElement, this.parentNode, 'top') + 'px'; this.wrapper.nativeElement.style.left = 'auto'; this.wrapper.nativeElement.style.position = 'relative'; break; } case 'remain': { if (this.wrapper.nativeElement.style.position !== 'fixed' || this.wrapper.nativeElement.style.position !== 'absolute') { this.wrapper.nativeElement.style.top = this.calculateRelativePosition(this.wrapper.nativeElement, this.parentNode, 'top') + 'px'; this.wrapper.nativeElement.style.left = 'auto'; this.wrapper.nativeElement.style.position = 'absolute'; // 要先转为absolute再计算,否则如果处于非fixed影响计算 } this.wrapper.nativeElement.style.top = this.calculateRemainPosition(this.wrapper.nativeElement, this.parentNode, this.container) + 'px'; this.wrapper.nativeElement.style.left = this.calculateRelativePosition(this.wrapper.nativeElement, this.parentNode, 'left') + 'px'; this.wrapper.nativeElement.style.position = 'relative'; break; } default: } } calculateRelativePosition(element, relativeElement, direction) { const key = { left: ['left', 'Left'], top: ['top', 'Top'], }; if (this.windowRef.window && this.windowRef.window.getComputedStyle) { const computedStyle = this.windowRef.window.getComputedStyle(relativeElement); return (element.getBoundingClientRect()[key[direction][0]] - relativeElement.getBoundingClientRect()[key[direction][0]] - parseInt(computedStyle['padding' + key[direction][1]], 10) - parseInt(computedStyle['border' + key[direction][1] + 'Width'], 10)); } } calculateRemainPosition(element, relativeElement, container) { if (this.windowRef.window && this.windowRef.window.getComputedStyle) { const computedStyle = this.windowRef.window.getComputedStyle(container); const result = container.getBoundingClientRect().height - element.getBoundingClientRect().height + container.getBoundingClientRect().top - relativeElement.getBoundingClientRect().top - parseInt(computedStyle.paddingTop, 10) - parseInt(computedStyle.borderTopWidth, 10) - parseInt(computedStyle.paddingBottom, 10) - parseInt(computedStyle.borderBottomWidth, 10); return result < 0 ? 0 : result; } } initScrollStatus(target) { const scrollTargets = target === this.windowRef.window ? [this.windowRef.document.documentElement, this.windowRef.document.body] : [target]; let flag = false; scrollTargets.forEach((scrollTarget) => { if (scrollTarget.scrollTop && scrollTarget.scrollTop > 0) { flag = true; } }); if (flag) { setTimeout(this.scrollHandler); } } /** * 提供给业务自己触发 * 用法: * 1.捕获所有sticky:```@ViewChildren(StickyComponent) stickies;``` * 2.触发刷新:当需要手动触发更新的时候,比如订阅数据返回后页面高度发生变化可以调用 ```stickies.forEach(sticky => sticky.recalculatePosition());``` * 慎用,少用, 使用太多会影响性能。 */ recalculatePosition() { this.initScrollStatus(this.scrollTarget); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: StickyComponent, deps: [{ token: i0.ElementRef }, { token: i1.WindowRef }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: StickyComponent, selector: "d-sticky", inputs: { zIndex: "zIndex", container: "container", view: "view", scrollTarget: "scrollTarget" }, outputs: { statusChange: "statusChange" }, host: { listeners: { "window:resize": "throttle()" }, properties: { "style.position": "this.hostPosition" } }, viewQueries: [{ propertyName: "wrapper", first: true, predicate: ["stickyWrapper"], descendants: true, static: true }], ngImport: i0, template: ` <div #stickyWrapper [style.zIndex]="zIndex"> <ng-content></ng-content> </div> `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: StickyComponent, decorators: [{ type: Component, args: [{ selector: 'd-sticky', template: ` <div #stickyWrapper [style.zIndex]="zIndex"> <ng-content></ng-content> </div> `, preserveWhitespaces: false, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.WindowRef }], propDecorators: { hostPosition: [{ type: HostBinding, args: ['style.position'] }], zIndex: [{ type: Input }], container: [{ type: Input }], view: [{ type: Input }], scrollTarget: [{ type: Input }], statusChange: [{ type: Output }], wrapper: [{ type: ViewChild, args: ['stickyWrapper', { static: true }] }], throttle: [{ type: HostListener, args: ['window:resize'] }] } }); class StickyModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: StickyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: StickyModule, declarations: [StickyComponent], imports: [WindowRefModule], exports: [StickyComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: StickyModule, imports: [WindowRefModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: StickyModule, decorators: [{ type: NgModule, args: [{ imports: [ WindowRefModule ], declarations: [ StickyComponent ], exports: [ StickyComponent, ], }] }] }); /** * Generated bundle index. Do not edit. */ export { StickyComponent, StickyModule }; //# sourceMappingURL=ng-devui-sticky.mjs.map