mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 21:15:24 +08:00
perf(content): scrollview magic activated on demand
This commit is contained in:
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user