From 7e9bad509255b642f6099366231ad86fe6eaddad Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Thu, 16 Mar 2017 18:17:24 +0100 Subject: [PATCH] perf(content): scrollview magic activated on demand --- src/components/content/content.ts | 34 ++++---- .../infinite-scroll/infinite-scroll.ts | 5 +- .../virtual-scroll/virtual-scroll.ts | 9 +- src/util/events.ts | 17 ++-- src/util/scroll-view.ts | 83 ++++++++++++------- 5 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/components/content/content.ts b/src/components/content/content.ts index 2a9101ee2b..5355e238e4 100644 --- a/src/components/content/content.ts +++ b/src/components/content/content.ts @@ -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 { Config } from '../../config/config'; @@ -123,7 +123,7 @@ export { ScrollEvent } from '../../util/scroll-view'; changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None }) -export class Content extends Ion implements OnDestroy, OnInit { +export class Content extends Ion implements OnDestroy { /** @internal */ _cTop: number; /** @internal */ @@ -339,7 +339,12 @@ export class Content extends Ion implements OnDestroy, OnInit { this._imgReqBfr = config.getNumber('imgRequestBuffer', 1400); this._imgRndBfr = config.getNumber('imgRenderBuffer', 400); 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) { // content has a view controller @@ -366,7 +371,7 @@ export class Content extends Ion implements OnDestroy, OnInit { /** * @private */ - ngOnInit() { + enableScrollListener() { assert(this.getFixedElement(), 'fixed 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(); // subscribe to the scroll start - scroll.scrollStart.subscribe(ev => { + scroll.onScrollStart = (ev) => { this.ionScrollStart.emit(ev); - }); + }; // subscribe to every scroll move - scroll.scroll.subscribe(ev => { + scroll.onScroll = (ev) => { // remind the app that it's currently scrolling this._app.setScrolling(); @@ -388,14 +393,16 @@ export class Content extends Ion implements OnDestroy, OnInit { this.ionScroll.emit(ev); this.imgsUpdate(); - }); + }; // subscribe to the scroll end - scroll.scrollEnd.subscribe(ev => { + scroll.onScrollEnd = (ev) => { this.ionScrollEnd.emit(ev); this.imgsUpdate(); - }); + }; + + scroll.setEnabled(); } /** @@ -466,13 +473,6 @@ export class Content extends Ion implements OnDestroy, OnInit { 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 * and footers. This effect can easily be seen by setting the toolbar diff --git a/src/components/infinite-scroll/infinite-scroll.ts b/src/components/infinite-scroll/infinite-scroll.ts index dfac48b118..f83ce91ec1 100644 --- a/src/components/infinite-scroll/infinite-scroll.ts +++ b/src/components/infinite-scroll/infinite-scroll.ts @@ -366,9 +366,8 @@ export class InfiniteScroll { if (this._init) { if (shouldListen) { if (!this._scLsn) { - this._scLsn = this._content.ionScroll.subscribe((ev: ScrollEvent) => { - this._onScroll(ev); - }); + this._scLsn = this._content.ionScroll.subscribe(this._onScroll.bind(this)); + this._content.enableScrollListener(); } } else { this._scLsn && this._scLsn.unsubscribe(); diff --git a/src/components/virtual-scroll/virtual-scroll.ts b/src/components/virtual-scroll/virtual-scroll.ts index 3ac9169606..3b023bc3e8 100644 --- a/src/components/virtual-scroll/virtual-scroll.ts +++ b/src/components/virtual-scroll/virtual-scroll.ts @@ -708,17 +708,10 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy { */ private _listeners() { 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._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this)); this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this)); + this._content.enableScrollListener(); } } diff --git a/src/util/events.ts b/src/util/events.ts index 5624ecb03d..a85231ec90 100644 --- a/src/util/events.ts +++ b/src/util/events.ts @@ -134,22 +134,23 @@ export function setupEvents(plt: Platform, dom: DomController): Events { let el = doc.elementFromPoint(plt.width() / 2, plt.height() / 2); if (!el) { return; } - let contentEle = el.closest('.scroll-content'); + let contentEle = el.closest('.scroll-content'); if (contentEle) { - var scroll = new ScrollView(plt, dom); + var style = contentEle.style; + var scroll = new ScrollView(plt, dom, false); scroll.init(contentEle, 0, 0); // We need to stop scrolling if it's happening and scroll up - (contentEle.style)['WebkitBackfaceVisibility'] = 'hidden'; - (contentEle.style)['WebkitTransform'] = 'translate3d(0,0,0)'; + style['WebkitBackfaceVisibility'] = 'hidden'; + style['WebkitTransform'] = 'translate3d(0,0,0)'; dom.write(function() { - contentEle.style.overflow = 'hidden'; + style.overflow = 'hidden'; function finish() { - contentEle.style.overflow = ''; - (contentEle.style)['WebkitBackfaceVisibility'] = ''; - (contentEle.style)['WebkitTransform'] = ''; + style.overflow = ''; + style['WebkitBackfaceVisibility'] = ''; + style['WebkitTransform'] = ''; } let didScrollTimeout = plt.timeout(() => { diff --git a/src/util/scroll-view.ts b/src/util/scroll-view.ts index 14229b15cc..339f019621 100644 --- a/src/util/scroll-view.ts +++ b/src/util/scroll-view.ts @@ -1,4 +1,3 @@ -import { Subject } from 'rxjs/Subject'; import { assert } from './util'; import { DomController, DomCallback } from '../platform/dom-controller'; @@ -9,10 +8,13 @@ import { pointerCoord } from './dom'; export class ScrollView { ev: ScrollEvent; isScrolling = false; - scrollStart = new Subject(); - scroll = new Subject(); - scrollEnd = new Subject(); - initialized: boolean; + onScrollStart: (ev: ScrollEvent) => void; + onScroll: (ev: ScrollEvent) => void; + onScrollEnd: (ev: ScrollEvent) => void; + initialized: boolean = false; + enabled: boolean = false; + contentTop: number; + contentBottom: number; private _el: HTMLElement; private _js: boolean; @@ -22,7 +24,12 @@ export class ScrollView { private _endTmr: Function; - constructor(private _plt: Platform, private _dom: DomController) { + constructor( + private _plt: Platform, + private _dom: DomController, + virtualScrollEventAssist: boolean + ) { + this._js = virtualScrollEventAssist; this.ev = { timeStamp: 0, scrollTop: 0, @@ -41,27 +48,47 @@ export class ScrollView { velocityX: 0, directionY: 'down', directionX: null, - domWrite: function(fn: DomCallback, ctx?: any): void { - _dom.write(fn, ctx); - } + domWrite: _dom.write.bind(_dom) }; } 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) { this.initialized = true; - assert(ele, 'scroll-view, element can not be null'); - this._el = ele; - - if (this._js) { - this.enableJsScroll(contentTop, contentBottom); - } else { - this.enableNativeScrolling(); + if (this.enabled) { + 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) { + this.enableJsScroll(); + } else { + this.enableNativeScrolling(); + } + } + private enableNativeScrolling() { this._js = false; if (!this._el) { @@ -103,7 +130,7 @@ export class ScrollView { positions.length = 0; // emit only on the first scroll event - self.scrollStart.next(ev); + self.onScrollStart(ev); } // actively scrolling @@ -147,13 +174,13 @@ export class ScrollView { ev.velocityY = ev.velocityX = 0; // emit that the scroll has ended - self.scrollEnd && self.scrollEnd.next(ev); + self.onScrollEnd(ev); self._endTmr = null; } // emit on each scroll event - self.scroll.next(ev); + self.onScroll(ev); // debounce for a moment after the last scroll event 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 * WKWebView does not have this issue, only UIWebView does. */ - enableJsScroll(contentTop: number, contentBottom: number) { + enableJsScroll() { const self = this; self._js = true; const ele = self._el; @@ -200,7 +227,7 @@ export class ScrollView { function setMax() { if (!max) { // ******** 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; // emit on each scroll event - self.scroll.next(ev); + self.onScroll(ev); self._dom.write(() => { // ******** DOM WRITE **************** @@ -240,7 +267,7 @@ export class ScrollView { ev.velocityY = ev.velocityX = 0; // 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; // emit only on the first scroll event - self.scrollStart.next(ev); + self.onScrollStart(ev); } self._dom.write(() => { @@ -295,7 +322,7 @@ export class ScrollView { if (!positions.length && self.isScrolling) { self.isScrolling = false; ev.velocityY = ev.velocityX = 0; - self.scrollEnd && self.scrollEnd.next(ev); + self.onScrollEnd(ev); return; } @@ -333,7 +360,7 @@ export class ScrollView { } else { self.isScrolling = false; ev.velocityY = 0; - self.scrollEnd && self.scrollEnd.next(ev); + self.onScrollEnd(ev); } positions.length = 0; @@ -520,9 +547,7 @@ export class ScrollView { this._endTmr && this._dom.cancel(this._endTmr); this._lsn && this._lsn(); - this.scrollStart && this.scrollStart.unsubscribe(); - this.scroll && this.scroll.unsubscribe(); - this.scrollEnd && this.scrollEnd.unsubscribe(); + this.onScrollStart = this.onScroll = this.onScrollEnd = null; let ev = this.ev; ev.domWrite = ev.contentElement = ev.fixedElement = ev.scrollElement = ev.headerElement = null;