[Perf] Fix slow animate function

This commit is contained in:
Alexander Zinchuk 2022-11-13 17:05:44 +04:00
parent a2b851e34c
commit 610574c23a
3 changed files with 121 additions and 17 deletions

99
src/util/FrameDebugger.ts Normal file
View File

@ -0,0 +1,99 @@
const RANDOM = 0.95;
const DEBOUNCE = 3000;
export default class FrameDebugger {
private durations: number[] = [];
private startedAtByFrameKey: Record<string, number | undefined> = {};
private passedFrames: string[] = [];
private timeout: number | undefined;
constructor(private name: string = '[No name]') {
}
onFrameStart(frameKey = '0') {
if (this.passedFrames.includes(frameKey)) {
// debugger
}
if (this.startedAtByFrameKey[frameKey]) {
return;
}
this.startedAtByFrameKey[frameKey] = performance.now();
}
onFrameEnd(frameKey = '0', onAnimationEnd?: AnyToVoidFunction) {
if (!this.startedAtByFrameKey[frameKey]) {
return;
}
const duration = performance.now() - this.startedAtByFrameKey[frameKey]!;
if (this.passedFrames.includes(frameKey)) {
// debugger
}
this.passedFrames.push(frameKey);
this.durations.push(duration);
this.startedAtByFrameKey[frameKey] = undefined;
if (Math.random() < RANDOM) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
// eslint-disable-next-line no-restricted-globals
this.timeout = self.setTimeout(() => {
if (!this.durations.length) {
return;
}
const max = Math.max(...this.durations);
const min = Math.max(...this.durations);
const maxIndex = this.durations.indexOf(max);
const minIndex = this.durations.indexOf(min);
const reduced = this.durations.slice();
reduced.splice(maxIndex, 1);
reduced.splice(minIndex, 1);
const avg = reduced.reduce((acc, cur) => acc + cur, 0) / this.durations.length;
// eslint-disable-next-line no-console
console.log(
'!!!',
this.name,
'total frames:',
this.durations.length,
', avg duration:',
avg.toFixed(2),
', max duration:',
Math.max(...reduced).toFixed(2),
', min duration:',
Math.min(...reduced).toFixed(2),
);
onAnimationEnd?.();
this.reset();
}, DEBOUNCE);
}
reset() {
this.durations = [];
this.startedAtByFrameKey = {};
this.passedFrames = [];
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
}
}

View File

@ -24,9 +24,17 @@ export function animateSingle(tick: Function, instance?: AnimationInstance) {
}
export function animate(tick: Function) {
fastRaf(() => {
if (tick()) {
animate(tick);
}
});
}
export function animateInstantly(tick: Function) {
if (tick()) {
fastRaf(() => {
animate(tick);
animateInstantly(tick);
});
}
}
@ -72,7 +80,7 @@ export function animateNumber({
const t0 = Date.now();
let canceled = false;
animate(() => {
animateInstantly(() => {
if (canceled) return false;
const t1 = Date.now();
let t = (t1 - t0) / duration;

View File

@ -2,7 +2,6 @@ import { getGlobal } from '../global';
import { ANIMATION_LEVEL_MIN } from '../config';
import { animate } from './animation';
import { fastRaf } from './schedulers';
const DEFAULT_DURATION = 300;
@ -62,23 +61,21 @@ function scrollWithJs(container: HTMLElement, left: number, duration: number) {
});
const startAt = Date.now();
fastRaf(() => {
animate(() => {
if (isStopped) return false;
animate(() => {
if (isStopped) return false;
const t = Math.min((Date.now() - startAt) / duration, 1);
const t = Math.min((Date.now() - startAt) / duration, 1);
const currentPath = path * (1 - transition(t));
container.scrollLeft = Math.round(target - currentPath);
const currentPath = path * (1 - transition(t));
container.scrollLeft = Math.round(target - currentPath);
if (t >= 1) {
container.style.scrollSnapType = '';
container.dataset.scrollId = undefined;
stopById.delete(id);
resolve();
}
return t < 1;
});
if (t >= 1) {
container.style.scrollSnapType = '';
container.dataset.scrollId = undefined;
stopById.delete(id);
resolve();
}
return t < 1;
});
return promise;