ng-devui
Version:
DevUI components based on Angular
264 lines (259 loc) • 12.8 kB
JavaScript
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