[Perf] Fix heavy animation event for fastSmoothScroll, refactoring

This commit is contained in:
Alexander Zinchuk 2021-07-19 01:56:35 +03:00
parent 7c275da3f8
commit 7c28bfac70
3 changed files with 65 additions and 46 deletions

View File

@ -6,7 +6,7 @@ const ANIMATION_END_EVENT = 'tt-event-heavy-animation-end';
let timeout: number | undefined; let timeout: number | undefined;
let isAnimating = false; let isAnimating = false;
export const dispatchHeavyAnimationEvent = (duration: number) => { export const dispatchHeavyAnimationEvent = (duration?: number) => {
if (!isAnimating) { if (!isAnimating) {
isAnimating = true; isAnimating = true;
document.dispatchEvent(new Event(ANIMATION_START_EVENT)); document.dispatchEvent(new Event(ANIMATION_START_EVENT));
@ -17,11 +17,18 @@ export const dispatchHeavyAnimationEvent = (duration: number) => {
timeout = undefined; timeout = undefined;
} }
timeout = window.setTimeout(() => { if (duration) {
timeout = window.setTimeout(() => {
isAnimating = false;
document.dispatchEvent(new Event(ANIMATION_END_EVENT));
timeout = undefined;
}, duration);
}
return () => {
isAnimating = false; isAnimating = false;
document.dispatchEvent(new Event(ANIMATION_END_EVENT)); document.dispatchEvent(new Event(ANIMATION_END_EVENT));
timeout = undefined; };
}, duration);
}; };
export default ( export default (

View File

@ -8,7 +8,6 @@ import {
FAST_SMOOTH_SHORT_TRANSITION_MAX_DISTANCE, FAST_SMOOTH_SHORT_TRANSITION_MAX_DISTANCE,
} from '../config'; } from '../config';
import { dispatchHeavyAnimationEvent } from '../hooks/useHeavyAnimationCheck'; import { dispatchHeavyAnimationEvent } from '../hooks/useHeavyAnimationCheck';
import { fastRaf } from './schedulers';
import { animateSingle } from './animation'; import { animateSingle } from './animation';
let isAnimating = false; let isAnimating = false;
@ -37,92 +36,104 @@ export default function fastSmoothScroll(
return; return;
} }
const { offsetTop } = element; const scrollFrom = calculateScrollFrom(container, element, maxDistance, forceDirection);
if (forceDirection === undefined) {
const offset = offsetTop - container.scrollTop;
if (offset < -maxDistance) {
container.scrollTop += (offset + maxDistance);
} else if (offset > maxDistance) {
container.scrollTop += (offset - maxDistance);
}
} else if (forceDirection === FocusDirection.Up) {
container.scrollTop = offsetTop + maxDistance;
} else if (forceDirection === FocusDirection.Down) {
container.scrollTop = Math.max(0, offsetTop - maxDistance);
}
if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) { if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) {
forceDuration = 0; forceDuration = 0;
} }
isAnimating = true; scrollWithJs(container, element, scrollFrom, position, margin, forceDuration, forceCurrentContainerHeight);
fastRaf(() => {
scrollWithJs(container, element, position, margin, forceDuration, forceCurrentContainerHeight);
});
} }
export function isAnimatingScroll() { export function isAnimatingScroll() {
return isAnimating; return isAnimating;
} }
function calculateScrollFrom(
container: HTMLElement,
element: HTMLElement,
maxDistance = FAST_SMOOTH_MAX_DISTANCE,
forceDirection?: FocusDirection,
) {
const { offsetTop: elementTop } = element;
const { scrollTop } = container;
if (forceDirection === undefined) {
const offset = elementTop - container.scrollTop;
if (offset < -maxDistance) {
return scrollTop + (offset + maxDistance);
} else if (offset > maxDistance) {
return scrollTop + (offset - maxDistance);
}
} else if (forceDirection === FocusDirection.Up) {
return elementTop + maxDistance;
} else if (forceDirection === FocusDirection.Down) {
return Math.max(0, elementTop - maxDistance);
}
return scrollTop;
}
function scrollWithJs( function scrollWithJs(
container: HTMLElement, container: HTMLElement,
element: HTMLElement, element: HTMLElement,
scrollFrom: number,
position: ScrollLogicalPosition | 'centerOrTop', position: ScrollLogicalPosition | 'centerOrTop',
margin = 0, margin = 0,
forceDuration?: number, forceDuration?: number,
forceCurrentContainerHeight?: boolean, forceCurrentContainerHeight?: boolean,
) { ) {
const { offsetTop: elementTop, offsetHeight: elementHeight } = element; const { offsetTop: elementTop, offsetHeight: elementHeight } = element;
const { scrollTop, offsetHeight: containerHeight, scrollHeight } = container; const { scrollTop: currentScrollTop, offsetHeight: containerHeight, scrollHeight } = container;
const targetContainerHeight = !forceCurrentContainerHeight && container.dataset.normalHeight const targetContainerHeight = !forceCurrentContainerHeight && container.dataset.normalHeight
? Number(container.dataset.normalHeight) ? Number(container.dataset.normalHeight)
: containerHeight; : containerHeight;
if (currentScrollTop !== scrollFrom) {
container.scrollTop = scrollFrom;
}
let path!: number; let path!: number;
switch (position) { switch (position) {
case 'start': case 'start':
path = (elementTop - margin) - scrollTop; path = (elementTop - margin) - scrollFrom;
break; break;
case 'end': case 'end':
path = (elementTop + elementHeight + margin) - (scrollTop + targetContainerHeight); path = (elementTop + elementHeight + margin) - (scrollFrom + targetContainerHeight);
break; break;
// 'nearest' is not supported yet // 'nearest' is not supported yet
case 'nearest': case 'nearest':
case 'center': case 'center':
case 'centerOrTop': case 'centerOrTop':
path = elementHeight < targetContainerHeight path = elementHeight < targetContainerHeight
? (elementTop + elementHeight / 2) - (scrollTop + targetContainerHeight / 2) ? (elementTop + elementHeight / 2) - (scrollFrom + targetContainerHeight / 2)
: (elementTop - margin) - scrollTop; : (elementTop - margin) - scrollFrom;
break; break;
} }
if (path < 0) { if (path < 0) {
const remainingPath = -scrollTop; const remainingPath = -scrollFrom;
path = Math.max(path, remainingPath); path = Math.max(path, remainingPath);
} else if (path > 0) { } else if (path > 0) {
const remainingPath = scrollHeight - (scrollTop + targetContainerHeight); const remainingPath = scrollHeight - (scrollFrom + targetContainerHeight);
path = Math.min(path, remainingPath); path = Math.min(path, remainingPath);
} }
if (path === 0) { if (path === 0) {
isAnimating = false;
return; return;
} }
const target = container.scrollTop + path; const target = scrollFrom + path;
if (forceDuration === 0) { if (forceDuration === 0) {
container.scrollTop = target; container.scrollTop = target;
isAnimating = false;
return; return;
} }
isAnimating = true;
const absPath = Math.abs(path); const absPath = Math.abs(path);
const transition = absPath < FAST_SMOOTH_SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition; const transition = absPath < FAST_SMOOTH_SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition;
const duration = forceDuration || ( const duration = forceDuration || (
@ -130,16 +141,20 @@ function scrollWithJs(
+ (absPath / FAST_SMOOTH_MAX_DISTANCE) * (FAST_SMOOTH_MAX_DURATION - FAST_SMOOTH_MIN_DURATION) + (absPath / FAST_SMOOTH_MAX_DISTANCE) * (FAST_SMOOTH_MAX_DURATION - FAST_SMOOTH_MIN_DURATION)
); );
const startAt = Date.now(); const startAt = Date.now();
const onHeavyAnimationStop = dispatchHeavyAnimationEvent();
dispatchHeavyAnimationEvent(duration);
animateSingle(() => { animateSingle(() => {
const t = Math.min((Date.now() - startAt) / duration, 1); const t = Math.min((Date.now() - startAt) / duration, 1);
const currentPath = path * (1 - transition(t)); const currentPath = path * (1 - transition(t));
container.scrollTop = Math.round(target - currentPath); container.scrollTop = Math.round(target - currentPath);
isAnimating = t < 1; isAnimating = t < 1;
if (!isAnimating) {
onHeavyAnimationStop();
}
return isAnimating; return isAnimating;
}); });
} }

View File

@ -1,9 +1,8 @@
import { getGlobal } from '../lib/teact/teactn'; import { getGlobal } from '../lib/teact/teactn';
import { fastRaf } from './schedulers';
import { animate } from './animation';
import { IS_IOS } from './environment';
import { ANIMATION_LEVEL_MIN } from '../config'; import { ANIMATION_LEVEL_MIN } from '../config';
import { IS_IOS } from './environment';
import { animate } from './animation';
const DEFAULT_DURATION = 300; const DEFAULT_DURATION = 300;
@ -19,9 +18,7 @@ export default function fastSmoothScrollHorizontal(container: HTMLElement, left:
...(duration && { behavior: 'smooth' }), ...(duration && { behavior: 'smooth' }),
}); });
} else { } else {
fastRaf(() => { scrollWithJs(container, left, duration);
scrollWithJs(container, left, duration);
});
} }
} }
@ -41,10 +38,10 @@ function scrollWithJs(container: HTMLElement, left: number, duration: number) {
return; return;
} }
const target = container.scrollLeft + path; const target = scrollLeft + path;
if (duration === 0) { if (duration === 0) {
container.scrollTop = target; container.scrollLeft = target;
return; return;
} }