From 7c28bfac708ce5a7abbf853c7a01a131743fb522 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 19 Jul 2021 01:56:35 +0300 Subject: [PATCH] [Perf] Fix heavy animation event for `fastSmoothScroll`, refactoring --- src/hooks/useHeavyAnimationCheck.ts | 15 +++-- src/util/fastSmoothScroll.ts | 83 +++++++++++++++----------- src/util/fastSmoothScrollHorizontal.ts | 13 ++-- 3 files changed, 65 insertions(+), 46 deletions(-) diff --git a/src/hooks/useHeavyAnimationCheck.ts b/src/hooks/useHeavyAnimationCheck.ts index 8060fa86..047d3e30 100644 --- a/src/hooks/useHeavyAnimationCheck.ts +++ b/src/hooks/useHeavyAnimationCheck.ts @@ -6,7 +6,7 @@ const ANIMATION_END_EVENT = 'tt-event-heavy-animation-end'; let timeout: number | undefined; let isAnimating = false; -export const dispatchHeavyAnimationEvent = (duration: number) => { +export const dispatchHeavyAnimationEvent = (duration?: number) => { if (!isAnimating) { isAnimating = true; document.dispatchEvent(new Event(ANIMATION_START_EVENT)); @@ -17,11 +17,18 @@ export const dispatchHeavyAnimationEvent = (duration: number) => { 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; document.dispatchEvent(new Event(ANIMATION_END_EVENT)); - timeout = undefined; - }, duration); + }; }; export default ( diff --git a/src/util/fastSmoothScroll.ts b/src/util/fastSmoothScroll.ts index b90d0867..f9f36ff5 100644 --- a/src/util/fastSmoothScroll.ts +++ b/src/util/fastSmoothScroll.ts @@ -8,7 +8,6 @@ import { FAST_SMOOTH_SHORT_TRANSITION_MAX_DISTANCE, } from '../config'; import { dispatchHeavyAnimationEvent } from '../hooks/useHeavyAnimationCheck'; -import { fastRaf } from './schedulers'; import { animateSingle } from './animation'; let isAnimating = false; @@ -37,92 +36,104 @@ export default function fastSmoothScroll( return; } - const { offsetTop } = element; - - 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); - } + const scrollFrom = calculateScrollFrom(container, element, maxDistance, forceDirection); if (getGlobal().settings.byKey.animationLevel === ANIMATION_LEVEL_MIN) { forceDuration = 0; } - isAnimating = true; - fastRaf(() => { - scrollWithJs(container, element, position, margin, forceDuration, forceCurrentContainerHeight); - }); + scrollWithJs(container, element, scrollFrom, position, margin, forceDuration, forceCurrentContainerHeight); } export function isAnimatingScroll() { 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( container: HTMLElement, element: HTMLElement, + scrollFrom: number, position: ScrollLogicalPosition | 'centerOrTop', margin = 0, forceDuration?: number, forceCurrentContainerHeight?: boolean, ) { 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 ? Number(container.dataset.normalHeight) : containerHeight; + if (currentScrollTop !== scrollFrom) { + container.scrollTop = scrollFrom; + } + let path!: number; switch (position) { case 'start': - path = (elementTop - margin) - scrollTop; + path = (elementTop - margin) - scrollFrom; break; case 'end': - path = (elementTop + elementHeight + margin) - (scrollTop + targetContainerHeight); + path = (elementTop + elementHeight + margin) - (scrollFrom + targetContainerHeight); break; // 'nearest' is not supported yet case 'nearest': case 'center': case 'centerOrTop': path = elementHeight < targetContainerHeight - ? (elementTop + elementHeight / 2) - (scrollTop + targetContainerHeight / 2) - : (elementTop - margin) - scrollTop; + ? (elementTop + elementHeight / 2) - (scrollFrom + targetContainerHeight / 2) + : (elementTop - margin) - scrollFrom; break; } if (path < 0) { - const remainingPath = -scrollTop; + const remainingPath = -scrollFrom; path = Math.max(path, remainingPath); } else if (path > 0) { - const remainingPath = scrollHeight - (scrollTop + targetContainerHeight); + const remainingPath = scrollHeight - (scrollFrom + targetContainerHeight); path = Math.min(path, remainingPath); } if (path === 0) { - isAnimating = false; - return; } - const target = container.scrollTop + path; + const target = scrollFrom + path; if (forceDuration === 0) { container.scrollTop = target; - isAnimating = false; - return; } + isAnimating = true; + const absPath = Math.abs(path); const transition = absPath < FAST_SMOOTH_SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition; const duration = forceDuration || ( @@ -130,16 +141,20 @@ function scrollWithJs( + (absPath / FAST_SMOOTH_MAX_DISTANCE) * (FAST_SMOOTH_MAX_DURATION - FAST_SMOOTH_MIN_DURATION) ); const startAt = Date.now(); + const onHeavyAnimationStop = dispatchHeavyAnimationEvent(); - dispatchHeavyAnimationEvent(duration); animateSingle(() => { const t = Math.min((Date.now() - startAt) / duration, 1); - const currentPath = path * (1 - transition(t)); + container.scrollTop = Math.round(target - currentPath); isAnimating = t < 1; + if (!isAnimating) { + onHeavyAnimationStop(); + } + return isAnimating; }); } diff --git a/src/util/fastSmoothScrollHorizontal.ts b/src/util/fastSmoothScrollHorizontal.ts index de859800..1d4463c2 100644 --- a/src/util/fastSmoothScrollHorizontal.ts +++ b/src/util/fastSmoothScrollHorizontal.ts @@ -1,9 +1,8 @@ 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 { IS_IOS } from './environment'; +import { animate } from './animation'; const DEFAULT_DURATION = 300; @@ -19,9 +18,7 @@ export default function fastSmoothScrollHorizontal(container: HTMLElement, left: ...(duration && { behavior: 'smooth' }), }); } else { - fastRaf(() => { - scrollWithJs(container, left, duration); - }); + scrollWithJs(container, left, duration); } } @@ -41,10 +38,10 @@ function scrollWithJs(container: HTMLElement, left: number, duration: number) { return; } - const target = container.scrollLeft + path; + const target = scrollLeft + path; if (duration === 0) { - container.scrollTop = target; + container.scrollLeft = target; return; }