perf(scroll): efficient scroll events and properties

This commit is contained in:
Adam Bradley
2016-12-05 16:50:05 -06:00
parent 32b76173a2
commit c377236dcb
15 changed files with 1028 additions and 511 deletions

View File

@@ -1,6 +1,7 @@
import { Directive, ElementRef, EventEmitter, Host, Input, NgZone, Output } from '@angular/core';
import { Content } from '../content/content';
import { Content, ScrollEvent } from '../content/content';
import { DomController } from '../../util/dom-controller';
/**
@@ -97,7 +98,7 @@ import { Content } from '../content/content';
export class InfiniteScroll {
_lastCheck: number = 0;
_highestY: number = 0;
_scLsn: Function;
_scLsn: any;
_thr: string = '15%';
_thrPx: number = 0;
_thrPc: number = 0.15;
@@ -156,31 +157,32 @@ export class InfiniteScroll {
constructor(
@Host() private _content: Content,
private _zone: NgZone,
private _elementRef: ElementRef
private _elementRef: ElementRef,
private _dom: DomController
) {
_content.setElementClass('has-infinite-scroll', true);
}
_onScroll() {
_onScroll(ev: ScrollEvent) {
if (this.state === STATE_LOADING || this.state === STATE_DISABLED) {
return 1;
}
let now = Date.now();
if (this._lastCheck + 32 > now) {
if (this._lastCheck + 32 > ev.timeStamp) {
// no need to check less than every XXms
return 2;
}
this._lastCheck = now;
this._lastCheck = ev.timeStamp;
let infiniteHeight = this._elementRef.nativeElement.scrollHeight;
// ******** DOM READ ****************
const infiniteHeight = this._elementRef.nativeElement.scrollHeight;
if (!infiniteHeight) {
// if there is no height of this element then do nothing
return 3;
}
let d = this._content.getContentDimensions();
// ******** DOM READ ****************
const d = this._content.getContentDimensions();
let reloadY = d.contentHeight;
if (this._thrPc) {
@@ -189,13 +191,18 @@ export class InfiniteScroll {
reloadY += this._thrPx;
}
let distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - reloadY;
// ******** DOM READS ABOVE / DOM WRITES BELOW ****************
const distanceFromInfinite = ((d.scrollHeight - infiniteHeight) - d.scrollTop) - reloadY;
if (distanceFromInfinite < 0) {
this._zone.run(() => {
if (this.state !== STATE_LOADING && this.state !== STATE_DISABLED) {
this.state = STATE_LOADING;
this.ionInfinite.emit(this);
}
// ******** DOM WRITE ****************
this._dom.write(() => {
this._zone.run(() => {
if (this.state !== STATE_LOADING && this.state !== STATE_DISABLED) {
this.state = STATE_LOADING;
this.ionInfinite.emit(this);
}
});
});
return 5;
}
@@ -238,12 +245,12 @@ export class InfiniteScroll {
if (this._init) {
if (shouldListen) {
if (!this._scLsn) {
this._zone.runOutsideAngular(() => {
this._scLsn = this._content.addScrollListener( this._onScroll.bind(this) );
this._scLsn = this._content.ionScroll.subscribe((ev: ScrollEvent) => {
this._onScroll(ev);
});
}
} else {
this._scLsn && this._scLsn();
this._scLsn && this._scLsn.unsubscribe();
this._scLsn = null;
}
}

View File

@@ -1,6 +1,6 @@
import { Content } from '../../content/content';
import { Content, ScrollEvent } from '../../content/content';
import { InfiniteScroll } from '../infinite-scroll';
import { mockConfig, mockElementRef, mockRenderer, mockZone } from '../../../util/mock-providers';
import { mockConfig, MockDomController, mockElementRef, mockRenderer, mockZone } from '../../../util/mock-providers';
describe('Infinite Scroll', () => {
@@ -17,7 +17,7 @@ describe('Infinite Scroll', () => {
setInfiniteScrollTop(300);
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(6);
});
@@ -30,37 +30,38 @@ describe('Infinite Scroll', () => {
setInfiniteScrollTop(300);
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(5);
});
it('should not run if there is not infinite element height', () => {
setInfiniteScrollTop(0);
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(3);
});
it('should not run again if ran less than 32ms ago', () => {
ev.timeStamp = Date.now();
inf._lastCheck = Date.now();
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(2);
});
it('should not run if state is disabled', () => {
inf.state = 'disabled';
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(1);
});
it('should not run if state is loading', () => {
inf.state = 'loading';
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(1);
});
it('should not run if not enabled', () => {
inf.state = 'disabled';
var result = inf._onScroll();
var result = inf._onScroll(ev);
expect(result).toEqual(1);
});
@@ -95,15 +96,19 @@ describe('Infinite Scroll', () => {
let content: Content;
let contentElementRef;
let infiniteElementRef;
let ev: ScrollEvent = {};
let dom: MockDomController;
beforeEach(() => {
contentElementRef = mockElementRef();
content = new Content(config, contentElementRef, mockRenderer(), null, null, null, null, null);
content = new Content(config, contentElementRef, mockRenderer(), null, null, null, null, null, dom);
content._scrollEle = document.createElement('div');
content._scrollEle.className = 'scroll-content';
infiniteElementRef = mockElementRef();
inf = new InfiniteScroll(content, mockZone(), infiniteElementRef);
dom = new MockDomController();
inf = new InfiniteScroll(content, mockZone(), infiniteElementRef, dom);
});
function setInfiniteScrollTop(scrollTop) {