mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 05:21:52 +08:00
perf(scroll): watchdog + simplification
This commit is contained in:
17
packages/core/src/components.d.ts
vendored
17
packages/core/src/components.d.ts
vendored
@ -42,20 +42,16 @@ import {
|
||||
GestureCallback,
|
||||
GestureDetail,
|
||||
} from './components/gesture/gesture';
|
||||
import {
|
||||
App,
|
||||
FrameworkDelegate as FrameworkDelegate2,
|
||||
} from '.';
|
||||
import {
|
||||
PickerButton,
|
||||
PickerColumn as PickerColumn2,
|
||||
} from './components/picker/picker';
|
||||
import {
|
||||
ScrollCallback,
|
||||
} from './components/scroll/scroll';
|
||||
import {
|
||||
SelectPopoverOption,
|
||||
} from './components/select-popover/select-popover';
|
||||
import {
|
||||
FrameworkDelegate as FrameworkDelegate2,
|
||||
} from '.';
|
||||
import {
|
||||
DomRenderFn,
|
||||
HeaderFn,
|
||||
@ -770,6 +766,7 @@ declare global {
|
||||
export interface IonContentAttributes extends HTMLAttributes {
|
||||
forceOverscroll?: boolean;
|
||||
fullscreen?: boolean;
|
||||
scrollEvents?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1240,7 +1237,7 @@ declare global {
|
||||
}
|
||||
namespace JSXElements {
|
||||
export interface IonInputShimsAttributes extends HTMLAttributes {
|
||||
app?: App;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2706,9 +2703,7 @@ declare global {
|
||||
export interface IonScrollAttributes extends HTMLAttributes {
|
||||
forceOverscroll?: boolean;
|
||||
mode?: string;
|
||||
onionScroll?: ScrollCallback;
|
||||
onionScrollEnd?: ScrollCallback;
|
||||
onionScrollStart?: ScrollCallback;
|
||||
scrollEvents?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { Config, NavEvent, OverlayController, PublicNav, PublicViewController }
|
||||
import { getOrAppendElement } from '../../utils/helpers';
|
||||
|
||||
const rootNavs = new Map<number, HTMLIonNavElement>();
|
||||
const ACTIVE_SCROLLING_TIME = 100;
|
||||
let backButtonActions: BackButtonAction[] = [];
|
||||
|
||||
@Component({
|
||||
@ -21,11 +20,9 @@ export class App {
|
||||
|
||||
private isDevice = false;
|
||||
private deviceHacks = false;
|
||||
private scrollTime = 0;
|
||||
|
||||
externalNavPromise: void | Promise<any> = null;
|
||||
externalNavOccuring = false;
|
||||
didScroll = false;
|
||||
|
||||
@Element() element: HTMLElement;
|
||||
@Event() exitApp: EventEmitter<ExitAppEventDetail>;
|
||||
@ -102,29 +99,6 @@ export class App {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean if the app is actively scrolling or not.
|
||||
* @return {boolean} returns true or false
|
||||
*/
|
||||
@Method()
|
||||
isScrolling(): boolean {
|
||||
const scrollTime = this.scrollTime;
|
||||
if (scrollTime === 0) {
|
||||
return false;
|
||||
}
|
||||
if (scrollTime < Date.now()) {
|
||||
this.scrollTime = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Method()
|
||||
setScrolling() {
|
||||
this.scrollTime = Date.now() + ACTIVE_SCROLLING_TIME;
|
||||
this.didScroll = true;
|
||||
}
|
||||
|
||||
@Method()
|
||||
getTopNavs(rootNavId = -1): PublicNav[] {
|
||||
return getTopNavsImpl(rootNavId);
|
||||
@ -142,7 +116,6 @@ export class App {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The back button event is triggered when the user presses the native
|
||||
* platform's back button, also referred to as the "hardware" back button.
|
||||
@ -244,7 +217,7 @@ export class App {
|
||||
render() {
|
||||
return [
|
||||
<ion-platform />,
|
||||
this.deviceHacks && <ion-input-shims app={this} />,
|
||||
this.deviceHacks && <ion-input-shims />,
|
||||
this.isDevice && <ion-tap-click />,
|
||||
this.isDevice && <ion-status-tap />,
|
||||
<slot></slot>
|
||||
|
@ -42,11 +42,6 @@ Returns an array of top level Navs
|
||||
Returns whether the application is enabled or not
|
||||
|
||||
|
||||
#### isScrolling()
|
||||
|
||||
Boolean if the app is actively scrolling or not.
|
||||
|
||||
|
||||
#### registerBackButtonAction()
|
||||
|
||||
The back button event is triggered when the user presses the native
|
||||
@ -69,9 +64,6 @@ This API is not meant for public usage and could
|
||||
change at any time
|
||||
|
||||
|
||||
#### setScrolling()
|
||||
|
||||
|
||||
#### updateExternalNavOccuring()
|
||||
|
||||
Updates whether an external navigation event is occuring
|
||||
|
@ -35,6 +35,9 @@ export class Content {
|
||||
*/
|
||||
@Prop() forceOverscroll: boolean;
|
||||
|
||||
|
||||
@Prop() scrollEvents = false;
|
||||
|
||||
@Listen('body:ionNavChanged')
|
||||
onNavChanged() {
|
||||
this.resize();
|
||||
@ -128,7 +131,10 @@ export class Content {
|
||||
this.resize();
|
||||
|
||||
return [
|
||||
<ion-scroll mode={this.mode} forceOverscroll={this.forceOverscroll}>
|
||||
<ion-scroll
|
||||
mode={this.mode}
|
||||
scrollEvents={this.scrollEvents}
|
||||
forceOverscroll={this.forceOverscroll}>
|
||||
<slot></slot>
|
||||
</ion-scroll>,
|
||||
<slot name='fixed'></slot>
|
||||
|
@ -34,6 +34,11 @@ and footers. This effect can easily be seen by setting the toolbar
|
||||
to transparent.
|
||||
|
||||
|
||||
#### scrollEvents
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
#### force-overscroll
|
||||
@ -54,6 +59,11 @@ and footers. This effect can easily be seen by setting the toolbar
|
||||
to transparent.
|
||||
|
||||
|
||||
#### scroll-events
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
#### scrollByPoint()
|
||||
|
@ -69,6 +69,13 @@
|
||||
</ion-footer>
|
||||
|
||||
<script>
|
||||
const content = document.getElementById('content');
|
||||
content.scrollEvents = true;
|
||||
content.addEventListener('ionScrollStart', () => console.log('scroll start'));
|
||||
content.addEventListener('ionScroll', (ev) => console.log('scroll', ev.detail));
|
||||
content.addEventListener('ionScrollEnd', () => console.log('scroll end'));
|
||||
|
||||
|
||||
function toggleFullscreen() {
|
||||
const content = document.getElementById('content');
|
||||
content.fullscreen = !content.fullscreen;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, State, Watch } from '@stencil/core';
|
||||
import { DomController, ScrollDetail } from '../../index';
|
||||
import { DomController } from '../../index';
|
||||
|
||||
const enum Position {
|
||||
Top = 'top',
|
||||
@ -106,10 +106,9 @@ export class InfiniteScroll {
|
||||
this.scrollEl = null;
|
||||
}
|
||||
|
||||
@Listen('ionScroll', {enabled: false})
|
||||
protected onScroll(ev: CustomEvent) {
|
||||
@Listen('scroll', {enabled: false})
|
||||
protected onScroll() {
|
||||
const scrollEl = this.scrollEl;
|
||||
const detail = ev.detail as ScrollDetail;
|
||||
if (!scrollEl || !this.canStart()) {
|
||||
return 1;
|
||||
}
|
||||
@ -119,7 +118,7 @@ export class InfiniteScroll {
|
||||
// if there is no height of this element then do nothing
|
||||
return 2;
|
||||
}
|
||||
const scrollTop = detail.scrollTop;
|
||||
const scrollTop = scrollEl.scrollTop;
|
||||
const scrollHeight = scrollEl.scrollHeight;
|
||||
const height = scrollEl.offsetHeight;
|
||||
const threshold = this.thrPc ? (height * this.thrPc) : this.thrPx;
|
||||
@ -221,7 +220,7 @@ export class InfiniteScroll {
|
||||
|
||||
private enableScrollEvents(shouldListen: boolean) {
|
||||
if (this.scrollEl) {
|
||||
this.enableListener(this, 'ionScroll', shouldListen, this.scrollEl);
|
||||
this.enableListener(this, 'scroll', shouldListen, this.scrollEl);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { App } from '../../..';
|
||||
|
||||
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
|
||||
|
||||
export default function enableInputBlurring(app: App) {
|
||||
export default function enableInputBlurring() {
|
||||
console.debug('Input: enableInputBlurring');
|
||||
|
||||
let focused = true;
|
||||
let didScroll = false;
|
||||
|
||||
function onScroll() {
|
||||
didScroll = true;
|
||||
}
|
||||
|
||||
function onFocusin() {
|
||||
focused = true;
|
||||
@ -13,8 +17,8 @@ export default function enableInputBlurring(app: App) {
|
||||
|
||||
function onTouchend(ev: any) {
|
||||
// if app did scroll return early
|
||||
if (app.didScroll) {
|
||||
app.didScroll = false;
|
||||
if (didScroll) {
|
||||
didScroll = false;
|
||||
return;
|
||||
}
|
||||
const active = document.activeElement as HTMLElement;
|
||||
@ -49,10 +53,12 @@ export default function enableInputBlurring(app: App) {
|
||||
}, 50);
|
||||
}
|
||||
|
||||
document.addEventListener('ionScrollStart', onScroll);
|
||||
document.addEventListener('focusin', onFocusin, true);
|
||||
document.addEventListener('touchend', onTouchend, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('ionScrollStart', onScroll, true);
|
||||
document.removeEventListener('focusin', onFocusin, true);
|
||||
document.removeEventListener('touchend', onTouchend, false);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component, Listen, Prop } from '@stencil/core';
|
||||
import { App, Config } from '../..';
|
||||
import { Config } from '../..';
|
||||
|
||||
import enableHideCaretOnScroll from './hacks/hide-caret';
|
||||
import enableInputBlurring from './hacks/input-blurring';
|
||||
@ -24,7 +24,6 @@ export class InputShims {
|
||||
private scrollAssistMap = new WeakMap<HTMLElement, Function>();
|
||||
|
||||
@Prop({context: 'config'}) config: Config;
|
||||
@Prop() app: App;
|
||||
|
||||
componentDidLoad() {
|
||||
this.keyboardHeight = this.config.getNumber('keyboardHeight', 290);
|
||||
@ -33,7 +32,7 @@ export class InputShims {
|
||||
|
||||
const inputBlurring = this.config.getBoolean('inputBlurring', true);
|
||||
if (inputBlurring && INPUT_BLURRING) {
|
||||
enableInputBlurring(this.app);
|
||||
enableInputBlurring();
|
||||
}
|
||||
|
||||
const scrollPadding = this.config.getBoolean('scrollPadding', true);
|
||||
|
@ -5,20 +5,6 @@
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
#### app
|
||||
|
||||
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
#### app
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -21,19 +21,9 @@ Note, the does not disable the system bounce on iOS. That is an OS level setting
|
||||
string
|
||||
|
||||
|
||||
#### onionScroll
|
||||
|
||||
|
||||
|
||||
|
||||
#### onionScrollEnd
|
||||
|
||||
|
||||
|
||||
|
||||
#### onionScrollStart
|
||||
|
||||
#### scrollEvents
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Attributes
|
||||
@ -52,26 +42,17 @@ Note, the does not disable the system bounce on iOS. That is an OS level setting
|
||||
string
|
||||
|
||||
|
||||
#### onion-scroll
|
||||
|
||||
|
||||
|
||||
|
||||
#### onion-scroll-end
|
||||
|
||||
|
||||
|
||||
|
||||
#### onion-scroll-start
|
||||
|
||||
#### scroll-events
|
||||
|
||||
boolean
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
#### ionScroll
|
||||
|
||||
Emitted while scrolling.
|
||||
Emitted while scrolling. This event is disabled by default.
|
||||
Look at the property: `scrollEvents`
|
||||
|
||||
|
||||
#### ionScrollEnd
|
||||
|
@ -14,9 +14,6 @@ ion-scroll {
|
||||
|
||||
contain: size style layout;
|
||||
|
||||
margin: 0 !important; // scss-lint:disable all
|
||||
padding: 0 !important; // scss-lint:disable all
|
||||
|
||||
.focus-input {
|
||||
@include margin(0);
|
||||
@include padding(0);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop } from '@stencil/core';
|
||||
import { Config, DomController, GestureDelegate, GestureDetail } from '../../index';
|
||||
|
||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
|
||||
import { Config, DomController, GestureDetail } from '../../index';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-scroll',
|
||||
@ -14,19 +13,14 @@ import { Config, DomController, GestureDelegate, GestureDetail } from '../../ind
|
||||
})
|
||||
export class Scroll {
|
||||
|
||||
private gesture: GestureDelegate;
|
||||
private positions: number[] = [];
|
||||
private tmr: any;
|
||||
private queued = false;
|
||||
private app: HTMLIonAppElement;
|
||||
private watchDog: any;
|
||||
private isScrolling = false;
|
||||
private detail: ScrollDetail = {};
|
||||
|
||||
private lastScroll = 0;
|
||||
private detail: ScrollDetail;
|
||||
private queued = false;
|
||||
@Element() private el: HTMLElement;
|
||||
|
||||
@Prop({ connect: 'ion-gesture-controller'}) gestureCtrl: HTMLIonGestureControllerElement;
|
||||
@Prop({ context: 'config'}) config: Config;
|
||||
@Prop({ context: 'enableListener'}) enableListener: EventListenerEnable;
|
||||
@Prop({ context: 'dom' }) dom: DomController;
|
||||
@Prop({ context: 'isServer' }) isServer: boolean;
|
||||
|
||||
@ -40,69 +34,76 @@ export class Scroll {
|
||||
*/
|
||||
@Prop({mutable: true}) forceOverscroll: boolean;
|
||||
|
||||
@Prop() onionScrollStart: ScrollCallback;
|
||||
@Prop() onionScroll: ScrollCallback;
|
||||
@Prop() onionScrollEnd: ScrollCallback;
|
||||
@Prop() scrollEvents = false;
|
||||
|
||||
/**
|
||||
* Emitted when the scroll has started.
|
||||
*/
|
||||
@Event() ionScrollStart: EventEmitter;
|
||||
@Event() ionScrollStart: EventEmitter<ScrollBaseDetail>;
|
||||
|
||||
/**
|
||||
* Emitted while scrolling.
|
||||
* Emitted while scrolling. This event is disabled by default.
|
||||
* Look at the property: `scrollEvents`
|
||||
*/
|
||||
@Event({bubbles: false}) ionScroll: EventEmitter;
|
||||
@Event({bubbles: false}) ionScroll: EventEmitter<ScrollDetail>;
|
||||
|
||||
/**
|
||||
* Emitted when the scroll has ended.
|
||||
*/
|
||||
@Event() ionScrollEnd: EventEmitter;
|
||||
@Event() ionScrollEnd: EventEmitter<ScrollBaseDetail>;
|
||||
|
||||
componentWillLoad() {
|
||||
return this.gestureCtrl.create({
|
||||
name: 'scroll',
|
||||
priority: 100,
|
||||
disableScroll: false,
|
||||
}).then((gesture) => {
|
||||
this.gesture = gesture;
|
||||
});
|
||||
constructor() {
|
||||
// Detail is used in a hot loop in the scroll event, by allocating it here
|
||||
// V8 will be able to inline any read/write to it since it's a monomorphic class.
|
||||
// https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html
|
||||
this.detail = {
|
||||
positions: [],
|
||||
scrollTop: 0,
|
||||
scrollLeft: 0,
|
||||
type: 'scroll',
|
||||
event: undefined,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
startTimeStamp: 0,
|
||||
currentX: 0,
|
||||
currentY: 0,
|
||||
velocityX: 0,
|
||||
velocityY: 0,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
timeStamp: 0,
|
||||
data: undefined
|
||||
};
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
componentWillLoad() {
|
||||
if (this.isServer) {
|
||||
return;
|
||||
}
|
||||
if (this.forceOverscroll === undefined) {
|
||||
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in window);
|
||||
}
|
||||
this.app = this.el.closest('ion-app');
|
||||
}
|
||||
|
||||
componentDidUnload() {
|
||||
this.gesture && this.gesture.destroy();
|
||||
this.gesture = this.detail = this.detail.event = null;
|
||||
}
|
||||
|
||||
// Native Scroll *************************
|
||||
|
||||
@Listen('scroll', { passive: true })
|
||||
onNativeScroll() {
|
||||
if (!this.queued) {
|
||||
onScroll(ev: UIEvent) {
|
||||
const timeStamp = Date.now();
|
||||
const didStart = !this.isScrolling;
|
||||
this.lastScroll = timeStamp;
|
||||
if (didStart) {
|
||||
this.onScrollStart();
|
||||
}
|
||||
if (!this.queued && this.scrollEvents) {
|
||||
this.queued = true;
|
||||
|
||||
this.dom.read(timeStamp => {
|
||||
this.queued = false;
|
||||
this.onScroll(timeStamp);
|
||||
this.detail.event = ev;
|
||||
updateScrollDetail(this.detail, this.el, timeStamp, didStart);
|
||||
this.ionScroll.emit(this.detail);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('touchend', {passive: true, capture: true })
|
||||
onTouchEnd(_ev: TouchEvent) {
|
||||
this.refocus();
|
||||
}
|
||||
|
||||
@Method()
|
||||
scrollToTop(duration: number): Promise<void> {
|
||||
return this.scrollToPoint(0, 0, duration);
|
||||
@ -212,115 +213,29 @@ export class Scroll {
|
||||
return promise;
|
||||
}
|
||||
|
||||
private onScroll(timeStamp: number) {
|
||||
const detail = this.detail;
|
||||
const positions = this.positions;
|
||||
const el = this.el;
|
||||
if (this.app) {
|
||||
this.app.setScrolling();
|
||||
}
|
||||
|
||||
detail.timeStamp = timeStamp;
|
||||
|
||||
// get the current scrollTop
|
||||
// ******** DOM READ ****************
|
||||
detail.scrollTop = el.scrollTop;
|
||||
|
||||
// get the current scrollLeft
|
||||
// ******** DOM READ ****************
|
||||
detail.scrollLeft = el.scrollLeft;
|
||||
|
||||
|
||||
if (!this.isScrolling) {
|
||||
// currently not scrolling, so this is a scroll start
|
||||
private onScrollStart() {
|
||||
this.isScrolling = true;
|
||||
|
||||
// remember the start positions
|
||||
detail.startY = detail.scrollTop;
|
||||
detail.startX = detail.scrollLeft;
|
||||
|
||||
// new scroll, so do some resets
|
||||
detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0;
|
||||
|
||||
// emit only on the first scroll event
|
||||
if (this.onionScrollStart) {
|
||||
this.onionScrollStart(detail);
|
||||
}
|
||||
this.gesture.capture();
|
||||
this.ionScrollStart.emit(detail);
|
||||
}
|
||||
detail.deltaY = (detail.scrollTop - detail.startY);
|
||||
detail.deltaX = (detail.scrollLeft - detail.startX);
|
||||
|
||||
// actively scrolling
|
||||
positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp);
|
||||
|
||||
// move pointer to position measured 100ms ago
|
||||
const timeRange = timeStamp - 100;
|
||||
let startPos = positions.length - 1;
|
||||
|
||||
while (startPos > 0 && positions[startPos] > timeRange) {
|
||||
startPos -= 3;
|
||||
}
|
||||
|
||||
if (startPos > 3) {
|
||||
// compute relative movement between these two points
|
||||
const frequency = 1 / (positions[startPos] - timeStamp);
|
||||
const movedY = positions[startPos - 1] - detail.scrollLeft;
|
||||
const movedX = positions[startPos - 2] - detail.scrollTop;
|
||||
|
||||
// based on XXms compute the movement to apply for each render step
|
||||
// velocity = space/time = s*(1/t) = s*frequency
|
||||
detail.velocityX = movedX * frequency;
|
||||
detail.velocityY = movedY * frequency;
|
||||
} else {
|
||||
detail.velocityX = 0;
|
||||
detail.velocityY = 0;
|
||||
}
|
||||
|
||||
clearTimeout(this.tmr);
|
||||
this.tmr = setTimeout(() => {
|
||||
|
||||
// haven't scrolled in a while, so it's a scrollend
|
||||
this.isScrolling = false;
|
||||
|
||||
this.dom.read(timeStamp => {
|
||||
if (!this.isScrolling) {
|
||||
this.onEnd(timeStamp);
|
||||
}
|
||||
this.ionScrollStart.emit({
|
||||
isScrolling: true
|
||||
});
|
||||
}, 80);
|
||||
|
||||
// emit on each scroll event
|
||||
if (this.onionScroll) {
|
||||
this.onionScroll(detail);
|
||||
} else {
|
||||
this.ionScroll.emit(detail);
|
||||
// watchdog
|
||||
this.watchDog = setInterval(() => {
|
||||
if (this.lastScroll < Date.now() - 120) {
|
||||
this.onScrollEnd();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private onEnd(timeStamp: number) {
|
||||
const detail = this.detail;
|
||||
private onScrollEnd() {
|
||||
|
||||
detail.timeStamp = timeStamp;
|
||||
|
||||
// emit that the scroll has ended
|
||||
this.gesture.release();
|
||||
|
||||
if (this.onionScrollEnd) {
|
||||
this.onionScrollEnd(detail);
|
||||
}
|
||||
this.ionScrollEnd.emit(detail);
|
||||
this.refocus();
|
||||
}
|
||||
|
||||
private refocus() {
|
||||
// TODO: renable when desktop testing is done
|
||||
// setTimeout(() => {
|
||||
// if (document.activeElement === document.body) {
|
||||
// (this.el.querySelector('.focus-input') as HTMLElement).focus();
|
||||
// }
|
||||
// });
|
||||
clearInterval(this.watchDog);
|
||||
this.watchDog = null;
|
||||
this.isScrolling = false;
|
||||
this.ionScrollEnd.emit({
|
||||
isScrolling: false
|
||||
});
|
||||
}
|
||||
|
||||
hostData() {
|
||||
@ -339,18 +254,67 @@ export class Scroll {
|
||||
</div>
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface ScrollDetail extends GestureDetail {
|
||||
// ******** DOM READ ****************
|
||||
function updateScrollDetail(
|
||||
detail: ScrollDetail,
|
||||
el: HTMLElement,
|
||||
timeStamp: number,
|
||||
didStart: boolean
|
||||
) {
|
||||
const scrollTop = el.scrollTop;
|
||||
const scrollLeft = el.scrollLeft;
|
||||
const positions = detail.positions;
|
||||
if (didStart) {
|
||||
// remember the start positions
|
||||
detail.startTimeStamp = timeStamp;
|
||||
detail.startY = scrollTop;
|
||||
detail.startX = scrollLeft;
|
||||
positions.length = 0;
|
||||
}
|
||||
|
||||
detail.timeStamp = timeStamp;
|
||||
detail.currentY = detail.scrollTop = scrollTop;
|
||||
detail.currentX = detail.scrollLeft = scrollLeft;
|
||||
detail.deltaY = scrollTop - detail.startY;
|
||||
detail.deltaX = scrollLeft - detail.startX;
|
||||
|
||||
// actively scrolling
|
||||
positions.push(scrollTop, scrollLeft, timeStamp);
|
||||
|
||||
// move pointer to position measured 100ms ago
|
||||
const timeRange = timeStamp - 100;
|
||||
let startPos = positions.length - 1;
|
||||
|
||||
while (startPos > 0 && positions[startPos] > timeRange) {
|
||||
startPos -= 3;
|
||||
}
|
||||
|
||||
if (startPos > 3) {
|
||||
// compute relative movement between these two points
|
||||
const frequency = 1 / (positions[startPos] - timeStamp);
|
||||
const movedX = positions[startPos - 1] - scrollLeft;
|
||||
const movedY = positions[startPos - 2] - scrollTop;
|
||||
|
||||
// based on XXms compute the movement to apply for each render step
|
||||
// velocity = space/time = s*(1/t) = s*frequency
|
||||
detail.velocityX = movedX * frequency;
|
||||
detail.velocityY = movedY * frequency;
|
||||
} else {
|
||||
detail.velocityX = 0;
|
||||
detail.velocityY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ScrollDetail extends GestureDetail, ScrollBaseDetail {
|
||||
positions?: number[];
|
||||
scrollTop?: number;
|
||||
scrollLeft?: number;
|
||||
scrollHeight?: number;
|
||||
scrollWidth?: number;
|
||||
contentHeight?: number;
|
||||
contentWidth?: number;
|
||||
contentTop?: number;
|
||||
contentBottom?: number;
|
||||
}
|
||||
|
||||
export interface ScrollBaseDetail {
|
||||
isScrolling?: boolean;
|
||||
}
|
||||
|
||||
export interface ScrollCallback {
|
||||
|
2
packages/core/src/index.d.ts
vendored
2
packages/core/src/index.d.ts
vendored
@ -80,7 +80,7 @@ export {
|
||||
} from './components/router/router-utils';
|
||||
export { Row } from './components/row/row';
|
||||
export { Reorder } from './components/reorder/reorder';
|
||||
export { Scroll, ScrollCallback, ScrollDetail } from './components/scroll/scroll';
|
||||
export { Scroll } from './components/scroll/scroll';
|
||||
export { Searchbar } from './components/searchbar/searchbar';
|
||||
export { Segment } from './components/segment/segment';
|
||||
export { SegmentButton } from './components/segment-button/segment-button';
|
||||
|
@ -44,9 +44,12 @@ export function isCheckedProperty(a: any, b: any): boolean {
|
||||
return (a == b); // tslint:disable-line
|
||||
}
|
||||
|
||||
export function assert(bool: boolean, msg: string) {
|
||||
if (!bool) {
|
||||
console.error(msg);
|
||||
export function assert(actual: any, reason: string) {
|
||||
if (!actual) {
|
||||
const message = 'ASSERT: ' + reason;
|
||||
console.error(message);
|
||||
debugger; // tslint:disable-line
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +146,7 @@ export function getPageElement(el: HTMLElement) {
|
||||
if (tabs) {
|
||||
return tabs;
|
||||
}
|
||||
const page = el.closest('ion-page,.ion-page,page-inner');
|
||||
const page = el.closest('ion-app,ion-page,.ion-page,page-inner');
|
||||
if (page) {
|
||||
return page;
|
||||
}
|
||||
|
Reference in New Issue
Block a user