mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-08 23:58:13 +08:00
perf(scroll): filter velocity using exponential moving window
This commit is contained in:
@ -5,7 +5,6 @@ export interface ScrollBaseDetail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrollDetail extends GestureDetail, ScrollBaseDetail {
|
export interface ScrollDetail extends GestureDetail, ScrollBaseDetail {
|
||||||
positions: number[];
|
|
||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
scrollLeft: number;
|
scrollLeft: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,30 @@ export class Scroll {
|
|||||||
private watchDog: any;
|
private watchDog: any;
|
||||||
private isScrolling = false;
|
private isScrolling = false;
|
||||||
private lastScroll = 0;
|
private lastScroll = 0;
|
||||||
private detail: ScrollDetail;
|
|
||||||
private queued = false;
|
private queued = false;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
private detail: ScrollDetail = {
|
||||||
|
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,
|
||||||
|
isScrolling: true,
|
||||||
|
};
|
||||||
|
|
||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
@Prop({ context: 'config' }) config!: Config;
|
@Prop({ context: 'config' }) config!: Config;
|
||||||
@ -51,31 +72,6 @@ export class Scroll {
|
|||||||
*/
|
*/
|
||||||
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;
|
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;
|
||||||
|
|
||||||
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,
|
|
||||||
isScrolling: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillLoad() {
|
componentWillLoad() {
|
||||||
if (this.forceOverscroll === undefined) {
|
if (this.forceOverscroll === undefined) {
|
||||||
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in this.win);
|
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in this.win);
|
||||||
@ -116,10 +112,7 @@ export class Scroll {
|
|||||||
/** Scroll to the bottom of the component */
|
/** Scroll to the bottom of the component */
|
||||||
@Method()
|
@Method()
|
||||||
scrollToBottom(duration: number): Promise<void> {
|
scrollToBottom(duration: number): Promise<void> {
|
||||||
const y = (this.el)
|
const y = this.el.scrollHeight - this.el.clientHeight;
|
||||||
? this.el.scrollHeight - this.el.clientHeight
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return this.scrollToPoint(0, y, duration);
|
return this.scrollToPoint(0, y, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,9 +202,8 @@ export class Scroll {
|
|||||||
// chill out for a frame first
|
// chill out for a frame first
|
||||||
this.queue.write(() => {
|
this.queue.write(() => {
|
||||||
this.queue.write(timeStamp => {
|
this.queue.write(timeStamp => {
|
||||||
// TODO: review stencilt type of timeStamp
|
startTime = timeStamp;
|
||||||
startTime = timeStamp!;
|
step(timeStamp);
|
||||||
step(timeStamp!);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -263,49 +255,32 @@ export class Scroll {
|
|||||||
function updateScrollDetail(
|
function updateScrollDetail(
|
||||||
detail: ScrollDetail,
|
detail: ScrollDetail,
|
||||||
el: HTMLElement,
|
el: HTMLElement,
|
||||||
timeStamp: number,
|
timestamp: number,
|
||||||
didStart: boolean
|
didStart: boolean
|
||||||
) {
|
) {
|
||||||
const scrollTop = el.scrollTop;
|
const prevX = detail.currentX;
|
||||||
const scrollLeft = el.scrollLeft;
|
const prevY = detail.currentY;
|
||||||
const positions = detail.positions;
|
const prevT = detail.timeStamp;
|
||||||
|
const currentX = el.scrollLeft;
|
||||||
|
const currentY = el.scrollTop;
|
||||||
if (didStart) {
|
if (didStart) {
|
||||||
// remember the start positions
|
// remember the start positions
|
||||||
detail.startTimeStamp = timeStamp;
|
detail.startTimeStamp = timestamp;
|
||||||
detail.startY = scrollTop;
|
detail.startX = currentX;
|
||||||
detail.startX = scrollLeft;
|
detail.startY = currentY;
|
||||||
positions.length = 0;
|
detail.velocityX = detail.velocityY = 0;
|
||||||
}
|
}
|
||||||
|
detail.timeStamp = timestamp;
|
||||||
|
detail.currentX = detail.scrollLeft = currentX;
|
||||||
|
detail.currentY = detail.scrollTop = currentY;
|
||||||
|
detail.deltaX = currentX - detail.startX;
|
||||||
|
detail.deltaY = currentY - detail.startY;
|
||||||
|
|
||||||
detail.timeStamp = timeStamp;
|
const timeDelta = timestamp - prevT;
|
||||||
detail.currentY = detail.scrollTop = scrollTop;
|
if (timeDelta > 0 && timeDelta < 100) {
|
||||||
detail.currentX = detail.scrollLeft = scrollLeft;
|
const velocityX = (currentX - prevX) / timeDelta;
|
||||||
detail.deltaY = scrollTop - detail.startY;
|
const velocityY = (currentY - prevY) / timeDelta;
|
||||||
detail.deltaX = scrollLeft - detail.startX;
|
detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3;
|
||||||
|
detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user