perf(content): scrollview magic activated on demand

This commit is contained in:
Manu Mtz.-Almeida
2017-03-16 18:17:24 +01:00
parent 963cdcbe76
commit 7e9bad5092
5 changed files with 83 additions and 65 deletions

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, NgZone, OnDestroy, Optional, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core';
import { App } from '../app/app'; import { App } from '../app/app';
import { Config } from '../../config/config'; import { Config } from '../../config/config';
@ -123,7 +123,7 @@ export { ScrollEvent } from '../../util/scroll-view';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class Content extends Ion implements OnDestroy, OnInit { export class Content extends Ion implements OnDestroy {
/** @internal */ /** @internal */
_cTop: number; _cTop: number;
/** @internal */ /** @internal */
@ -339,7 +339,12 @@ export class Content extends Ion implements OnDestroy, OnInit {
this._imgReqBfr = config.getNumber('imgRequestBuffer', 1400); this._imgReqBfr = config.getNumber('imgRequestBuffer', 1400);
this._imgRndBfr = config.getNumber('imgRenderBuffer', 400); this._imgRndBfr = config.getNumber('imgRenderBuffer', 400);
this._imgVelMax = config.getNumber('imgVelocityMax', 3); this._imgVelMax = config.getNumber('imgVelocityMax', 3);
this._scroll = new ScrollView(_plt, _dom);
// use JS scrolling for iOS UIWebView
// goal is to completely remove this when iOS
// fully supports scroll events
// listen to JS scroll events
this._scroll = new ScrollView(_plt, _dom, config.getBoolean('virtualScrollEventAssist'));
if (viewCtrl) { if (viewCtrl) {
// content has a view controller // content has a view controller
@ -366,7 +371,7 @@ export class Content extends Ion implements OnDestroy, OnInit {
/** /**
* @private * @private
*/ */
ngOnInit() { enableScrollListener() {
assert(this.getFixedElement(), 'fixed element was not found'); assert(this.getFixedElement(), 'fixed element was not found');
assert(this.getScrollElement(), 'scroll element was not found'); assert(this.getScrollElement(), 'scroll element was not found');
@ -375,12 +380,12 @@ export class Content extends Ion implements OnDestroy, OnInit {
scroll.ev.scrollElement = this.getScrollElement(); scroll.ev.scrollElement = this.getScrollElement();
// subscribe to the scroll start // subscribe to the scroll start
scroll.scrollStart.subscribe(ev => { scroll.onScrollStart = (ev) => {
this.ionScrollStart.emit(ev); this.ionScrollStart.emit(ev);
}); };
// subscribe to every scroll move // subscribe to every scroll move
scroll.scroll.subscribe(ev => { scroll.onScroll = (ev) => {
// remind the app that it's currently scrolling // remind the app that it's currently scrolling
this._app.setScrolling(); this._app.setScrolling();
@ -388,14 +393,16 @@ export class Content extends Ion implements OnDestroy, OnInit {
this.ionScroll.emit(ev); this.ionScroll.emit(ev);
this.imgsUpdate(); this.imgsUpdate();
}); };
// subscribe to the scroll end // subscribe to the scroll end
scroll.scrollEnd.subscribe(ev => { scroll.onScrollEnd = (ev) => {
this.ionScrollEnd.emit(ev); this.ionScrollEnd.emit(ev);
this.imgsUpdate(); this.imgsUpdate();
}); };
scroll.setEnabled();
} }
/** /**
@ -466,13 +473,6 @@ export class Content extends Ion implements OnDestroy, OnInit {
return this._scroll.scrollToBottom(duration); return this._scroll.scrollToBottom(duration);
} }
/**
* @private
*/
enableJsScroll() {
this._scroll.enableJsScroll(this._cTop, this._cBottom);
}
/** /**
* @input {boolean} If true, the content will scroll behind the headers * @input {boolean} If true, the content will scroll behind the headers
* and footers. This effect can easily be seen by setting the toolbar * and footers. This effect can easily be seen by setting the toolbar

View File

@ -366,9 +366,8 @@ export class InfiniteScroll {
if (this._init) { if (this._init) {
if (shouldListen) { if (shouldListen) {
if (!this._scLsn) { if (!this._scLsn) {
this._scLsn = this._content.ionScroll.subscribe((ev: ScrollEvent) => { this._scLsn = this._content.ionScroll.subscribe(this._onScroll.bind(this));
this._onScroll(ev); this._content.enableScrollListener();
});
} }
} else { } else {
this._scLsn && this._scLsn.unsubscribe(); this._scLsn && this._scLsn.unsubscribe();

View File

@ -708,17 +708,10 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
*/ */
private _listeners() { private _listeners() {
if (!this._scrollSub) { if (!this._scrollSub) {
if (this._config.getBoolean('virtualScrollEventAssist')) {
// use JS scrolling for iOS UIWebView
// goal is to completely remove this when iOS
// fully supports scroll events
// listen to JS scroll events
this._content.enableJsScroll();
}
this._resizeSub = this._plt.resize.subscribe(this.resize.bind(this)); this._resizeSub = this._plt.resize.subscribe(this.resize.bind(this));
this._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this)); this._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this));
this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this)); this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this));
this._content.enableScrollListener();
} }
} }

View File

@ -134,22 +134,23 @@ export function setupEvents(plt: Platform, dom: DomController): Events {
let el = <HTMLElement>doc.elementFromPoint(plt.width() / 2, plt.height() / 2); let el = <HTMLElement>doc.elementFromPoint(plt.width() / 2, plt.height() / 2);
if (!el) { return; } if (!el) { return; }
let contentEle = <HTMLElement>el.closest('.scroll-content'); let contentEle = <any>el.closest('.scroll-content');
if (contentEle) { if (contentEle) {
var scroll = new ScrollView(plt, dom); var style = contentEle.style;
var scroll = new ScrollView(plt, dom, false);
scroll.init(contentEle, 0, 0); scroll.init(contentEle, 0, 0);
// We need to stop scrolling if it's happening and scroll up // We need to stop scrolling if it's happening and scroll up
(<any>contentEle.style)['WebkitBackfaceVisibility'] = 'hidden'; style['WebkitBackfaceVisibility'] = 'hidden';
(<any>contentEle.style)['WebkitTransform'] = 'translate3d(0,0,0)'; style['WebkitTransform'] = 'translate3d(0,0,0)';
dom.write(function() { dom.write(function() {
contentEle.style.overflow = 'hidden'; style.overflow = 'hidden';
function finish() { function finish() {
contentEle.style.overflow = ''; style.overflow = '';
(<any>contentEle.style)['WebkitBackfaceVisibility'] = ''; style['WebkitBackfaceVisibility'] = '';
(<any>contentEle.style)['WebkitTransform'] = ''; style['WebkitTransform'] = '';
} }
let didScrollTimeout = plt.timeout(() => { let didScrollTimeout = plt.timeout(() => {

View File

@ -1,4 +1,3 @@
import { Subject } from 'rxjs/Subject';
import { assert } from './util'; import { assert } from './util';
import { DomController, DomCallback } from '../platform/dom-controller'; import { DomController, DomCallback } from '../platform/dom-controller';
@ -9,10 +8,13 @@ import { pointerCoord } from './dom';
export class ScrollView { export class ScrollView {
ev: ScrollEvent; ev: ScrollEvent;
isScrolling = false; isScrolling = false;
scrollStart = new Subject<ScrollEvent>(); onScrollStart: (ev: ScrollEvent) => void;
scroll = new Subject<ScrollEvent>(); onScroll: (ev: ScrollEvent) => void;
scrollEnd = new Subject<ScrollEvent>(); onScrollEnd: (ev: ScrollEvent) => void;
initialized: boolean; initialized: boolean = false;
enabled: boolean = false;
contentTop: number;
contentBottom: number;
private _el: HTMLElement; private _el: HTMLElement;
private _js: boolean; private _js: boolean;
@ -22,7 +24,12 @@ export class ScrollView {
private _endTmr: Function; private _endTmr: Function;
constructor(private _plt: Platform, private _dom: DomController) { constructor(
private _plt: Platform,
private _dom: DomController,
virtualScrollEventAssist: boolean
) {
this._js = virtualScrollEventAssist;
this.ev = { this.ev = {
timeStamp: 0, timeStamp: 0,
scrollTop: 0, scrollTop: 0,
@ -41,26 +48,46 @@ export class ScrollView {
velocityX: 0, velocityX: 0,
directionY: 'down', directionY: 'down',
directionX: null, directionX: null,
domWrite: function(fn: DomCallback, ctx?: any): void { domWrite: _dom.write.bind(_dom)
_dom.write(fn, ctx);
}
}; };
} }
init(ele: HTMLElement, contentTop: number, contentBottom: number) { init(ele: HTMLElement, contentTop: number, contentBottom: number) {
assert(ele, 'scroll-view, element can not be null');
this._el = ele;
this.initialized = true;
this.contentTop = contentTop;
this.contentBottom = contentBottom;
if (!this.initialized) { if (!this.initialized) {
this.initialized = true; this.initialized = true;
assert(ele, 'scroll-view, element can not be null'); if (this.enabled) {
this._el = ele; this.enable();
}
}
}
setEnabled() {
if (!this.enabled) {
this.enabled = true;
if (this.initialized) {
this.enable();
}
}
}
enable() {
assert(this.initialized, 'scroll must be initialized');
assert(this.enabled, 'scroll-view must be enabled');
assert(this._el, 'scroll-view, element can not be null');
if (this._js) { if (this._js) {
this.enableJsScroll(contentTop, contentBottom); this.enableJsScroll();
} else { } else {
this.enableNativeScrolling(); this.enableNativeScrolling();
} }
} }
}
private enableNativeScrolling() { private enableNativeScrolling() {
this._js = false; this._js = false;
@ -103,7 +130,7 @@ export class ScrollView {
positions.length = 0; positions.length = 0;
// emit only on the first scroll event // emit only on the first scroll event
self.scrollStart.next(ev); self.onScrollStart(ev);
} }
// actively scrolling // actively scrolling
@ -147,13 +174,13 @@ export class ScrollView {
ev.velocityY = ev.velocityX = 0; ev.velocityY = ev.velocityX = 0;
// emit that the scroll has ended // emit that the scroll has ended
self.scrollEnd && self.scrollEnd.next(ev); self.onScrollEnd(ev);
self._endTmr = null; self._endTmr = null;
} }
// emit on each scroll event // emit on each scroll event
self.scroll.next(ev); self.onScroll(ev);
// debounce for a moment after the last scroll event // debounce for a moment after the last scroll event
self._dom.cancel(self._endTmr); self._dom.cancel(self._endTmr);
@ -181,7 +208,7 @@ export class ScrollView {
* inertia then this can be burned to the ground. iOS's more modern * inertia then this can be burned to the ground. iOS's more modern
* WKWebView does not have this issue, only UIWebView does. * WKWebView does not have this issue, only UIWebView does.
*/ */
enableJsScroll(contentTop: number, contentBottom: number) { enableJsScroll() {
const self = this; const self = this;
self._js = true; self._js = true;
const ele = self._el; const ele = self._el;
@ -200,7 +227,7 @@ export class ScrollView {
function setMax() { function setMax() {
if (!max) { if (!max) {
// ******** DOM READ **************** // ******** DOM READ ****************
max = ele.scrollHeight - ele.parentElement.offsetHeight + contentTop + contentBottom; max = ele.scrollHeight - ele.parentElement.offsetHeight + self.contentTop + self.contentBottom;
} }
}; };
@ -221,7 +248,7 @@ export class ScrollView {
ev.scrollTop = self._t; ev.scrollTop = self._t;
// emit on each scroll event // emit on each scroll event
self.scroll.next(ev); self.onScroll(ev);
self._dom.write(() => { self._dom.write(() => {
// ******** DOM WRITE **************** // ******** DOM WRITE ****************
@ -240,7 +267,7 @@ export class ScrollView {
ev.velocityY = ev.velocityX = 0; ev.velocityY = ev.velocityX = 0;
// emit that the scroll has ended // emit that the scroll has ended
self.scrollEnd && self.scrollEnd.next(ev); self.onScrollEnd(ev);
} }
}); });
} }
@ -279,7 +306,7 @@ export class ScrollView {
self.isScrolling = true; self.isScrolling = true;
// emit only on the first scroll event // emit only on the first scroll event
self.scrollStart.next(ev); self.onScrollStart(ev);
} }
self._dom.write(() => { self._dom.write(() => {
@ -295,7 +322,7 @@ export class ScrollView {
if (!positions.length && self.isScrolling) { if (!positions.length && self.isScrolling) {
self.isScrolling = false; self.isScrolling = false;
ev.velocityY = ev.velocityX = 0; ev.velocityY = ev.velocityX = 0;
self.scrollEnd && self.scrollEnd.next(ev); self.onScrollEnd(ev);
return; return;
} }
@ -333,7 +360,7 @@ export class ScrollView {
} else { } else {
self.isScrolling = false; self.isScrolling = false;
ev.velocityY = 0; ev.velocityY = 0;
self.scrollEnd && self.scrollEnd.next(ev); self.onScrollEnd(ev);
} }
positions.length = 0; positions.length = 0;
@ -520,9 +547,7 @@ export class ScrollView {
this._endTmr && this._dom.cancel(this._endTmr); this._endTmr && this._dom.cancel(this._endTmr);
this._lsn && this._lsn(); this._lsn && this._lsn();
this.scrollStart && this.scrollStart.unsubscribe(); this.onScrollStart = this.onScroll = this.onScrollEnd = null;
this.scroll && this.scroll.unsubscribe();
this.scrollEnd && this.scrollEnd.unsubscribe();
let ev = this.ev; let ev = this.ev;
ev.domWrite = ev.contentElement = ev.fixedElement = ev.scrollElement = ev.headerElement = null; ev.domWrite = ev.contentElement = ev.fixedElement = ev.scrollElement = ev.headerElement = null;