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

@@ -95,32 +95,37 @@ export class DomController {
}
protected flush(timeStamp: number) {
let err;
try {
this.dispatch(timeStamp);
} finally {
this.q = false;
}
}
private dispatch(timeStamp: number) {
let i: number;
const r = this.r;
const rLen = r.length;
const w = this.w;
const wLen = w.length;
// ******** DOM READS ****************
for (i = 0; i < rLen; i++) {
r[i](timeStamp);
dispatch(timeStamp, this.r, this.w);
} catch (e) {
err = e;
}
// ******** DOM WRITES ****************
for (i = 0; i < wLen; i++) {
w[i](timeStamp);
this.q = false;
if (this.r.length || this.w.length) {
this.queue();
}
r.length = 0;
w.length = 0;
if (err) {
throw err;
}
}
}
function dispatch(timeStamp: number, r: Function[], w: Function[]) {
let task;
// ******** DOM READS ****************
while (task = r.shift()) {
task(timeStamp);
}
// ******** DOM WRITES ****************
while (task = w.shift()) {
task(timeStamp);
}
}

View File

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

View File

@@ -1,7 +1,8 @@
import { Injectable, NgZone } from '@angular/core';
import { Config } from '../config/config';
import { focusOutActiveElement, hasFocusedTextInput, nativeRaf, nativeTimeout, zoneRafFrames } from './dom';
import { DomController } from './dom-controller';
import { focusOutActiveElement, hasFocusedTextInput, nativeTimeout } from './dom';
import { Key } from './key';
@@ -23,7 +24,7 @@ import { Key } from './key';
@Injectable()
export class Keyboard {
constructor(config: Config, private _zone: NgZone) {
constructor(config: Config, private _zone: NgZone, private _dom: DomController) {
_zone.runOutsideAngular(() => {
this.focusOutline(config.get('focusOutline'), document);
@@ -92,10 +93,12 @@ export class Keyboard {
function checkKeyboard() {
console.debug(`keyboard, isOpen: ${self.isOpen()}`);
if (!self.isOpen() || checks > pollingChecksMax) {
zoneRafFrames(30, () => {
console.debug(`keyboard, closed`);
callback();
});
nativeTimeout(function() {
self._zone.run(function() {
console.debug(`keyboard, closed`);
callback();
});
}, 400);
} else {
nativeTimeout(checkKeyboard, pollingInternval);
@@ -112,11 +115,13 @@ export class Keyboard {
* Programmatically close the keyboard.
*/
close() {
console.debug(`keyboard, close()`);
nativeRaf(() => {
this._dom.read(() => {
if (hasFocusedTextInput()) {
// only focus out when a text input has focus
focusOutActiveElement();
console.debug(`keyboard, close()`);
this._dom.write(() => {
focusOutActiveElement();
});
}
});
}
@@ -141,7 +146,7 @@ export class Keyboard {
let isKeyInputEnabled = false;
function cssClass() {
nativeRaf(() => {
this._dom.write(() => {
document.body.classList[isKeyInputEnabled ? 'add' : 'remove']('focus-outline');
});
}

View File

@@ -3,13 +3,16 @@ import { Location } from '@angular/common';
import { AnimationOptions } from '../animations/animation';
import { App } from '../components/app/app';
import { IonicApp } from '../components/app/app-root';
import { Config } from '../config/config';
import { Content } from '../components/content/content';
import { DeepLinker } from '../navigation/deep-linker';
import { DomController } from './dom-controller';
import { GestureController } from '../gestures/gesture-controller';
import { Haptic } from './haptic';
import { IonicApp } from '../components/app/app-root';
import { Keyboard } from './keyboard';
import { Menu } from '../components/menu/menu';
import { ViewState, DeepLinkConfig } from '../navigation/nav-util';
import { NavControllerBase } from '../navigation/nav-controller-base';
import { OverlayPortal } from '../components/nav/overlay-portal';
import { PageTransition } from '../transitions/page-transition';
import { Platform } from '../platform/platform';
@@ -19,10 +22,8 @@ import { Tabs } from '../components/tabs/tabs';
import { TransitionController } from '../transitions/transition-controller';
import { UrlSerializer } from '../navigation/url-serializer';
import { ViewController } from '../navigation/view-controller';
import { ViewState, DeepLinkConfig } from '../navigation/nav-util';
import { NavControllerBase } from '../navigation/nav-controller-base';
import { Haptic } from './haptic';
import { DomController } from './dom-controller';
export const mockConfig = function(config?: any, url: string = '/', platform?: Platform) {
const c = new Config();
@@ -72,6 +73,10 @@ export const mockTrasitionController = function(config: Config) {
return trnsCtrl;
};
export const mockContent = function(): Content {
return new Content(mockConfig(), mockElementRef(), mockRenderer(), null, null, mockZone(), null, null, new MockDomController());
};
export const mockZone = function(): NgZone {
return new NgZone(false);
};
@@ -234,7 +239,9 @@ export const mockNavController = function(): NavControllerBase {
let zone = mockZone();
let keyboard = new Keyboard(config, zone);
let dom = new MockDomController();
let keyboard = new Keyboard(config, zone, dom);
let elementRef = mockElementRef();
@@ -248,8 +255,6 @@ export const mockNavController = function(): NavControllerBase {
let trnsCtrl = mockTrasitionController(config);
let dom = new DomController();
let nav = new NavControllerBase(
null,
app,
@@ -262,7 +267,7 @@ export const mockNavController = function(): NavControllerBase {
gestureCtrl,
trnsCtrl,
linker,
dom,
dom
);
nav._viewInit = function(enteringView: ViewController) {
@@ -285,7 +290,9 @@ export const mockNavController = function(): NavControllerBase {
export const mockOverlayPortal = function(app: App, config: Config, platform: Platform): OverlayPortal {
let zone = mockZone();
let keyboard = new Keyboard(config, zone);
let dom = new MockDomController();
let keyboard = new Keyboard(config, zone, dom);
let elementRef = mockElementRef();
@@ -301,8 +308,6 @@ export const mockOverlayPortal = function(app: App, config: Config, platform: Pl
let deepLinker = new DeepLinker(app, serializer, location);
let dom = new DomController();
return new OverlayPortal(
app,
config,
@@ -328,7 +333,9 @@ export const mockTab = function(parentTabs: Tabs): Tab {
let zone = mockZone();
let keyboard = new Keyboard(config, zone);
let dom = new MockDomController();
let keyboard = new Keyboard(config, zone, dom);
let elementRef = mockElementRef();
@@ -342,8 +349,6 @@ export const mockTab = function(parentTabs: Tabs): Tab {
let linker = mockDeepLinker(null, app);
let dom = new DomController();
let tab = new Tab(
parentTabs,
app,
@@ -402,6 +407,22 @@ export const mockHaptic = function (): Haptic {
return new Haptic(null);
};
export class MockDomController extends DomController {
private timeStamp = 0;
protected queue() {}
flush(done: any) {
setTimeout(() => {
const timeStamp = ++this.timeStamp;
super.flush(timeStamp);
done(timeStamp);
}, 0);
}
}
export class MockView {}
export class MockView1 {}
export class MockView2 {}

View File

@@ -1,40 +1,388 @@
import { CSS, pointerCoord, nativeRaf, rafFrames, cancelRaf } from '../util/dom';
import { Subject } from 'rxjs/Subject';
import { assert } from './util';
import { CSS, nativeRaf, pointerCoord, rafFrames } from './dom';
import { DomController } from './dom-controller';
import { eventOptions, listenEvent } from './ui-event-manager';
export class ScrollView {
private _el: HTMLElement;
private _js: boolean = false;
private _top: number = 0;
private _pos: Array<number>;
private _velocity: number;
private _max: number;
private _rafId: number;
private _cb: Function;
isPlaying: boolean;
isScrolling = false;
scrollStart = new Subject<ScrollEvent>();
scroll = new Subject<ScrollEvent>();
scrollEnd = new Subject<ScrollEvent>();
constructor(ele: HTMLElement) {
this._el = ele;
private _el: HTMLElement;
private _js: boolean;
private _t: number = 0;
private _l: number = 0;
private _lsn: Function;
private _endTmr: Function;
ev: ScrollEvent = {
directionY: ScrollDirection.Down,
directionX: null
};
constructor(private _dom: DomController) {}
init(ele: HTMLElement, contentTop: number, contentBottom: number) {
if (!this._el) {
assert(ele, 'scroll-view, element can not be null');
this._el = ele;
if (this._js) {
this.enableJsScroll(contentTop, contentBottom);
} else {
this.enableNativeScrolling();
}
}
}
getTop(): number {
if (this._js) {
return this._top;
private enableNativeScrolling() {
this._js = false;
if (!this._el) {
return;
}
return this._top = this._el.scrollTop;
console.debug(`ScrollView, enableNativeScrolling`);
const self = this;
const ev = self.ev;
const positions: number[] = [];
function scrollCallback(scrollEvent: UIEvent) {
ev.timeStamp = scrollEvent.timeStamp;
// get the current scrollTop
// ******** DOM READ ****************
ev.scrollTop = self.getTop();
// get the current scrollLeft
// ******** DOM READ ****************
ev.scrollLeft = self.getLeft();
if (!self.isScrolling) {
// currently not scrolling, so this is a scroll start
self.isScrolling = true;
// remember the start positions
ev.startY = ev.scrollTop;
ev.startX = ev.scrollLeft;
// new scroll, so do some resets
ev.velocityY = ev.velocityX = 0;
ev.deltaY = ev.deltaX = 0;
positions.length = 0;
// emit only on the first scroll event
self.scrollStart.next(ev);
}
// actively scrolling
positions.push(ev.scrollTop, ev.scrollLeft, ev.timeStamp);
if (positions.length > 3) {
// we've gotten at least 2 scroll events so far
ev.deltaY = (ev.scrollTop - ev.startY);
ev.deltaX = (ev.scrollLeft - ev.startX);
var endPos = (positions.length - 1);
var startPos = endPos;
var timeRange = (ev.timeStamp - 100);
// move pointer to position measured 100ms ago
for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 3) {
startPos = i;
}
if (startPos !== endPos) {
// compute relative movement between these two points
var timeOffset = (positions[endPos] - positions[startPos]);
var movedTop = (positions[startPos - 2] - positions[endPos - 2]);
var movedLeft = (positions[startPos - 1] - positions[endPos - 1]);
// based on XXms compute the movement to apply for each render step
ev.velocityY = ((movedTop / timeOffset) * FRAME_MS);
ev.velocityX = ((movedLeft / timeOffset) * FRAME_MS);
// figure out which direction we're scrolling
ev.directionY = (movedTop > 0 ? ScrollDirection.Up : ScrollDirection.Down);
ev.directionX = (movedLeft > 0 ? ScrollDirection.Left : ScrollDirection.Right);
}
}
// emit on each scroll event
self.scroll.next(ev);
// debounce for a moment after the last scroll event
self._endTmr && self._endTmr();
self._endTmr = rafFrames(5, function scrollEnd() {
// haven't scrolled in a while, so it's a scrollend
self.isScrolling = false;
// reset velocity, do not reset the directions or deltas
ev.velocityY = ev.velocityX = 0;
// emit that the scroll has ended
self.scrollEnd.next(ev);
});
};
// clear out any existing listeners (just to be safe)
self._lsn && self._lsn();
// assign the raw scroll listener
// note that it does not have a wrapping requestAnimationFrame on purpose
// a scroll event callback will always be right before the raf callback
// so there's little to no value of using raf here since it'll all ways immediately
// call the raf if it was set within the scroll event, so this will save us some time
const opts = eventOptions(false, false);
self._lsn = listenEvent(self._el, 'scroll', false, opts, scrollCallback);
}
/**
* @private
* JS Scrolling has been provided only as a temporary solution
* until iOS apps can take advantage of scroll events at all times.
* The goal is to eventually remove JS scrolling entirely. When we
* no longer have to worry about iOS not firing scroll events during
* 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) {
const self = this;
self._js = true;
const ele = self._el;
if (!ele) {
return;
}
console.debug(`ScrollView, enableJsScroll`);
const positions: number[] = [];
let rafCancel: Function;
let max: number;
const ev = self.ev;
ev.scrollLeft = 0;
ev.startX = 0;
ev.deltaX = 0;
ev.velocityX = 0;
ev.directionX = null;
function setMax() {
if (!max) {
// ******** DOM READ ****************
max = (ele.scrollHeight - ele.offsetHeight) + contentTop + contentBottom;
}
};
function jsScrollDecelerate(timeStamp: number) {
ev.timeStamp = timeStamp;
console.debug(`scroll-view, decelerate, velocity: ${ev.velocityY}`);
if (ev.velocityY) {
ev.velocityY *= DECELERATION_FRICTION;
// update top with updated velocity
// clamp top within scroll limits
setMax();
self._t = Math.min(Math.max(self._t + ev.velocityY, 0), max);
ev.scrollTop = self._t;
// emit on each scroll event
self.scroll.next(ev);
self._dom.write(() => {
// ******** DOM WRITE ****************
self.setTop(self._t);
if (self._t > 0 && self._t < max && Math.abs(ev.velocityY) > MIN_VELOCITY_CONTINUE_DECELERATION) {
rafCancel = self._dom.read(rafTimeStamp => {
jsScrollDecelerate(rafTimeStamp);
});
} else {
// haven't scrolled in a while, so it's a scrollend
self.isScrolling = false;
// reset velocity, do not reset the directions or deltas
ev.velocityY = ev.velocityX = 0;
// emit that the scroll has ended
self.scrollEnd.next(ev);
}
});
}
}
function jsScrollTouchStart(touchEvent: TouchEvent) {
positions.length = 0;
max = null;
positions.push(pointerCoord(touchEvent).y, touchEvent.timeStamp);
}
function jsScrollTouchMove(touchEvent: TouchEvent) {
if (!positions.length) {
return;
}
ev.timeStamp = touchEvent.timeStamp;
var y = pointerCoord(touchEvent).y;
// ******** DOM READ ****************
setMax();
self._t -= (y - positions[positions.length - 2]);
self._t = Math.min(Math.max(self._t, 0), max);
positions.push(y, ev.timeStamp);
if (!self.isScrolling) {
// remember the start position
ev.startY = self._t;
// new scroll, so do some resets
ev.velocityY = ev.deltaY = 0;
self.isScrolling = true;
// emit only on the first scroll event
self.scrollStart.next(ev);
}
self._dom.write(() => {
// ******** DOM WRITE ****************
self.setTop(self._t);
});
}
function jsScrollTouchEnd(touchEvent: TouchEvent) {
// figure out what the scroll position was about 100ms ago
self._dom.cancel(rafCancel);
if (!positions.length && self.isScrolling) {
self.isScrolling = false;
ev.velocityY = ev.velocityX = 0;
self.scrollEnd.next(ev);
return;
}
var y = pointerCoord(touchEvent).y;
positions.push(y, touchEvent.timeStamp);
var endPos = (positions.length - 1);
var startPos = endPos;
var timeRange = (touchEvent.timeStamp - 100);
// move pointer to position measured 100ms ago
for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 2) {
startPos = i;
}
if (startPos !== endPos) {
// compute relative movement between these two points
var timeOffset = (positions[endPos] - positions[startPos]);
var movedTop = (positions[startPos - 1] - positions[endPos - 1]);
// based on XXms compute the movement to apply for each render step
ev.velocityY = ((movedTop / timeOffset) * FRAME_MS);
// verify that we have enough velocity to start deceleration
if (Math.abs(ev.velocityY) > MIN_VELOCITY_START_DECELERATION) {
// ******** DOM READ ****************
setMax();
rafCancel = self._dom.read((rafTimeStamp: number) => {
jsScrollDecelerate(rafTimeStamp);
});
}
} else {
self.isScrolling = false;
ev.velocityY = ev.velocityX = 0;
self.scrollEnd.next(ev);
}
positions.length = 0;
}
const opts = eventOptions(false, true);
const unRegStart = listenEvent(ele, 'touchstart', false, opts, jsScrollTouchStart);
const unRegMove = listenEvent(ele, 'touchmove', false, opts, jsScrollTouchMove);
const unRegEnd = listenEvent(ele, 'touchend', false, opts, jsScrollTouchEnd);
ele.parentElement.classList.add('js-scroll');
// stop listening for actual scroll events
self._lsn && self._lsn();
// create an unregister for all of these events
self._lsn = () => {
unRegStart();
unRegMove();
unRegEnd();
ele.parentElement.classList.remove('js-scroll');
};
}
/**
* DOM READ
*/
getTop() {
if (this._js) {
return this._t;
}
return this._t = this._el.scrollTop || 0;
}
/**
* DOM READ
*/
getLeft() {
if (this._js) {
return 0;
}
return this._l = this._el.scrollLeft || 0;
}
/**
* DOM WRITE
*/
setTop(top: number) {
this._top = top;
this._t = top;
if (this._js) {
(<any>this._el.style)[CSS.transform] = `translate3d(0px,${top * -1}px,0px)`;
(<any>this._el.style)[CSS.transform] = `translate3d(${this._l * -1}px,${top * -1}px,0px)`;
} else {
this._el.scrollTop = top;
}
}
/**
* DOM WRITE
*/
setLeft(left: number) {
this._l = left;
if (this._js) {
(<any>this._el.style)[CSS.transform] = `translate3d(${left * -1}px,${this._t * -1}px,0px)`;
} else {
this._el.scrollLeft = left;
}
}
scrollTo(x: number, y: number, duration: number, done?: Function): Promise<any> {
// scroll animation loop w/ easing
// credit https://gist.github.com/dezinezync/5487119
@@ -68,17 +416,17 @@ export class ScrollView {
let attempts = 0;
// scroll loop
function step() {
function step(timeStamp: number) {
attempts++;
if (!self._el || !self.isPlaying || attempts > maxAttempts) {
self.isPlaying = false;
if (!self._el || !self.isScrolling || attempts > maxAttempts) {
self.isScrolling = false;
self._el.style.transform = ``;
done();
return;
}
let time = Math.min(1, ((Date.now() - startTime) / duration));
let time = Math.min(1, ((timeStamp - startTime) / duration));
// where .5 would be 50% of time on a linear scale easedT gives a
// fraction based on the easing method
@@ -89,25 +437,28 @@ export class ScrollView {
}
if (fromX !== x) {
self._el.scrollLeft = Math.floor((easedT * (x - fromX)) + fromX);
self.setLeft(Math.floor((easedT * (x - fromX)) + fromX));
}
if (easedT < 1) {
// do not use DomController here
// must use nativeRaf in order to fire in the next frame
nativeRaf(step);
} else {
self.isScrolling = false;
self._el.style.transform = ``;
done();
}
}
// start scroll loop
self.isPlaying = true;
self.isScrolling = true;
// chill out for a frame first
rafFrames(2, () => {
startTime = Date.now();
step();
rafFrames(2, (timeStamp) => {
startTime = timeStamp;
step(timeStamp);
});
return promise;
@@ -126,167 +477,51 @@ export class ScrollView {
}
stop() {
this.isPlaying = false;
}
/**
* @private
* JS Scrolling has been provided only as a temporary solution
* until iOS apps can take advantage of scroll events at all times.
* The goal is to eventually remove JS scrolling entirely. This
* method may be removed in the future.
*/
jsScroll(onScrollCallback: Function): Function {
this._js = true;
this._cb = onScrollCallback;
this._pos = [];
if (this._el) {
this._el.addEventListener('touchstart', this._start.bind(this));
this._el.addEventListener('touchmove', this._move.bind(this));
this._el.addEventListener('touchend', this._end.bind(this));
this._el.parentElement.classList.add('js-scroll');
}
return () => {
if (this._el) {
this._el.removeEventListener('touchstart', this._start.bind(this));
this._el.removeEventListener('touchmove', this._move.bind(this));
this._el.removeEventListener('touchend', this._end.bind(this));
this._el.parentElement.classList.remove('js-scroll');
}
};
}
/**
* @private
* Used for JS scrolling. May be removed in the future.
*/
private _start(ev: UIEvent) {
this._velocity = 0;
this._pos.length = 0;
this._max = null;
this._pos.push(pointerCoord(ev).y, Date.now());
}
/**
* @private
* Used for JS scrolling. May be removed in the future.
*/
private _move(ev: UIEvent) {
if (this._pos.length) {
let y = pointerCoord(ev).y;
// ******** DOM READ ****************
this._setMax();
this._top -= (y - this._pos[this._pos.length - 2]);
this._top = Math.min(Math.max(this._top, 0), this._max);
this._pos.push(y, Date.now());
// ******** DOM READ THEN DOM WRITE ****************
this._cb(this._top);
// ******** DOM WRITE ****************
this.setTop(this._top);
}
}
/**
* @private
* Used for JS scrolling. May be removed in the future.
*/
private _setMax() {
if (!this._max) {
// ******** DOM READ ****************
this._max = (this._el.offsetHeight - this._el.parentElement.offsetHeight + this._el.parentElement.offsetTop);
}
}
/**
* @private
* Used for JS scrolling. May be removed in the future.
*/
private _end(ev: UIEvent) {
// figure out what the scroll position was about 100ms ago
let positions = this._pos;
this._velocity = 0;
cancelRaf(this._rafId);
if (!positions.length) return;
let y = pointerCoord(ev).y;
positions.push(y, Date.now());
let endPos = (positions.length - 1);
let startPos = endPos;
let timeRange = (Date.now() - 100);
// move pointer to position measured 100ms ago
for (var i = endPos; i > 0 && positions[i] > timeRange; i -= 2) {
startPos = i;
}
if (startPos !== endPos) {
// compute relative movement between these two points
let timeOffset = (positions[endPos] - positions[startPos]);
let movedTop = (positions[startPos - 1] - positions[endPos - 1]);
// based on XXms compute the movement to apply for each render step
this._velocity = ((movedTop / timeOffset) * FRAME_MS);
// verify that we have enough velocity to start deceleration
if (Math.abs(this._velocity) > MIN_VELOCITY_START_DECELERATION) {
// ******** DOM READ ****************
this._setMax();
this._rafId = nativeRaf(this._decelerate.bind(this));
}
}
positions.length = 0;
}
/**
* @private
* Used for JS scrolling. May be removed in the future.
*/
private _decelerate() {
var self = this;
if (self._velocity) {
self._velocity *= DECELERATION_FRICTION;
// update top with updated velocity
// clamp top within scroll limits
self._top = Math.min(Math.max(self._top + self._velocity, 0), self._max);
// ******** DOM READ THEN DOM WRITE ****************
self._cb(self._top);
// ******** DOM WRITE ****************
self.setTop(self._top);
if (self._top > 0 && self._top < self._max && Math.abs(self._velocity) > MIN_VELOCITY_CONTINUE_DECELERATION) {
self._rafId = nativeRaf(self._decelerate.bind(self));
}
}
this.isScrolling = false;
}
/**
* @private
*/
destroy() {
this._velocity = 0;
this.scrollStart.unsubscribe();
this.scroll.unsubscribe();
this.scrollEnd.unsubscribe();
this.stop();
this._el = null;
this._lsn();
this._endTmr && this._endTmr();
this._el = this._dom = null;
}
}
export interface ScrollEvent {
scrollTop?: number;
scrollLeft?: number;
startY?: number;
startX?: number;
deltaY?: number;
deltaX?: number;
timeStamp?: number;
velocityY?: number;
velocityX?: number;
directionY?: ScrollDirection;
directionX?: ScrollDirection;
}
export enum ScrollDirection {
Up, Down, Left, Right
}
export interface DomFn {
(callback: Function): void;
}
const MIN_VELOCITY_START_DECELERATION = 4;
const MIN_VELOCITY_CONTINUE_DECELERATION = 0.12;
const DECELERATION_FRICTION = 0.97;

View File

@@ -191,14 +191,14 @@ export class UIEventManager {
return;
}
let zone = config.zone || this.zoneWrapped;
let opts;
let opts: any;
if (supportsOptions) {
opts = {};
if (config.passive === true) {
opts['passive'] = true;
opts.passive = true;
}
if (config.capture === true) {
opts['capture'] = true;
opts.capture = true;
}
} else {
if (config.passive === true) {
@@ -244,13 +244,13 @@ export class UIEventManager {
}
export function listenEvent(ele: any, eventName: string, zoneWrapped: boolean, option: any, callback: any): Function {
let rawEvent = (!zoneWrapped && '__zone_symbol__addEventListener' in ele);
const rawEvent = (!zoneWrapped && !!ele.__zone_symbol__addEventListener);
if (rawEvent) {
ele.__zone_symbol__addEventListener(eventName, callback, option);
assert('__zone_symbol__removeEventListener' in ele, 'native removeEventListener does not exist');
assert(!!ele.__zone_symbol__removeEventListener, 'native removeEventListener does not exist');
return () => ele.__zone_symbol__removeEventListener(eventName, callback, option);
} else {
ele.addEventListener(eventName, callback, option);
return () => ele.removeEventListener(eventName, callback, option);
}
ele.addEventListener(eventName, callback, option);
return () => ele.removeEventListener(eventName, callback, option);
}