Skip to content

Commit

Permalink
perf(scroll): filter velocity using exponential moving window
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat committed Aug 7, 2018
1 parent f944735 commit 419ef7b
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 73 deletions.
1 change: 0 additions & 1 deletion core/src/components/scroll/scroll-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export interface ScrollBaseDetail {
}

export interface ScrollDetail extends GestureDetail, ScrollBaseDetail {
positions: number[];
scrollTop: number;
scrollLeft: number;
}
Expand Down
119 changes: 47 additions & 72 deletions core/src/components/scroll/scroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,30 @@ export class Scroll {
private watchDog: any;
private isScrolling = false;
private lastScroll = 0;
private detail: ScrollDetail;
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;

@Prop({ context: 'config' }) config!: Config;
Expand Down Expand Up @@ -51,31 +72,6 @@ export class Scroll {
*/
@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() {
if (this.forceOverscroll === undefined) {
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in this.win);
Expand Down Expand Up @@ -116,10 +112,7 @@ export class Scroll {
/** Scroll to the bottom of the component */
@Method()
scrollToBottom(duration: number): Promise<void> {
const y = (this.el)
? this.el.scrollHeight - this.el.clientHeight
: 0;

const y = this.el.scrollHeight - this.el.clientHeight;
return this.scrollToPoint(0, y, duration);
}

Expand Down Expand Up @@ -209,9 +202,8 @@ export class Scroll {
// chill out for a frame first
this.queue.write(() => {
this.queue.write(timeStamp => {
// TODO: review stencilt type of timeStamp
startTime = timeStamp!;
step(timeStamp!);
startTime = timeStamp;
step(timeStamp);
});
});

Expand Down Expand Up @@ -263,49 +255,32 @@ export class Scroll {
function updateScrollDetail(
detail: ScrollDetail,
el: HTMLElement,
timeStamp: number,
timestamp: number,
didStart: boolean
) {
const scrollTop = el.scrollTop;
const scrollLeft = el.scrollLeft;
const positions = detail.positions;
const prevX = detail.currentX;
const prevY = detail.currentY;
const prevT = detail.timeStamp;
const currentX = el.scrollLeft;
const currentY = el.scrollTop;
if (didStart) {
// remember the start positions
detail.startTimeStamp = timeStamp;
detail.startY = scrollTop;
detail.startX = scrollLeft;
positions.length = 0;
detail.startTimeStamp = timestamp;
detail.startX = currentX;
detail.startY = currentY;
detail.velocityX = detail.velocityY = 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;
detail.timeStamp = timestamp;
detail.currentX = detail.scrollLeft = currentX;
detail.currentY = detail.scrollTop = currentY;
detail.deltaX = currentX - detail.startX;
detail.deltaY = currentY - detail.startY;

const timeDelta = timestamp - prevT;
if (timeDelta > 0 && timeDelta < 100) {
const velocityX = (currentX - prevX) / timeDelta;
const velocityY = (currentY - prevY) / timeDelta;
detail.velocityX = velocityX * 0.7 + detail.velocityX * 0.3;
detail.velocityY = velocityY * 0.7 + detail.velocityY * 0.3;
}
}

0 comments on commit 419ef7b

Please sign in to comment.