[Perf] Transition: Get rid of redundant slide wrappers
This commit is contained in:
parent
982ba8c3a7
commit
ecf4ffb7ae
@ -47,6 +47,7 @@ enum AppScreens {
|
|||||||
inactive,
|
inactive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TRANSITION_RENDER_COUNT = Object.keys(AppScreens).length / 2;
|
||||||
const INACTIVE_PAGE_TITLE = `${PAGE_TITLE} ${INACTIVE_MARKER}`;
|
const INACTIVE_PAGE_TITLE = `${PAGE_TITLE} ${INACTIVE_MARKER}`;
|
||||||
|
|
||||||
const App: FC<StateProps> = ({
|
const App: FC<StateProps> = ({
|
||||||
@ -204,7 +205,7 @@ const App: FC<StateProps> = ({
|
|||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UiLoader key="Loader" page={page} isMobile={isMobile}>
|
<UiLoader page={page} isMobile={isMobile}>
|
||||||
<Transition
|
<Transition
|
||||||
name="fade"
|
name="fade"
|
||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
@ -213,6 +214,7 @@ const App: FC<StateProps> = ({
|
|||||||
'full-height',
|
'full-height',
|
||||||
(activeKey === AppScreens.auth || prevActiveKey === AppScreens.auth) && 'is-auth',
|
(activeKey === AppScreens.auth || prevActiveKey === AppScreens.auth) && 'is-auth',
|
||||||
)}
|
)}
|
||||||
|
renderCount={TRANSITION_RENDER_COUNT}
|
||||||
>
|
>
|
||||||
{renderContent}
|
{renderContent}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
@ -484,19 +484,19 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
activeKey={contentType}
|
activeKey={contentType}
|
||||||
shouldCleanup
|
shouldCleanup
|
||||||
cleanupExceptionKey={ContentType.Main}
|
cleanupExceptionKey={ContentType.Main}
|
||||||
|
shouldWrap
|
||||||
|
wrapExceptionKey={ContentType.Main}
|
||||||
id="LeftColumn"
|
id="LeftColumn"
|
||||||
>
|
afterChildren={(
|
||||||
{(isActive) => (
|
<div
|
||||||
<>
|
className="resize-handle"
|
||||||
{renderContent(isActive)}
|
onMouseDown={initResize}
|
||||||
<div
|
onMouseUp={handleMouseUp}
|
||||||
className="resize-handle"
|
onDoubleClick={resetResize}
|
||||||
onMouseDown={initResize}
|
/>
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
onDoubleClick={resetResize}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
{renderContent}
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
> .Transition {
|
> .Transition {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@ -172,6 +172,8 @@ const LeftMain: FC<OwnProps> = ({
|
|||||||
activeKey={content}
|
activeKey={content}
|
||||||
shouldCleanup
|
shouldCleanup
|
||||||
cleanupExceptionKey={LeftColumnContent.ChatList}
|
cleanupExceptionKey={LeftColumnContent.ChatList}
|
||||||
|
shouldWrap
|
||||||
|
wrapExceptionKey={LeftColumnContent.ChatList}
|
||||||
>
|
>
|
||||||
{(isActive) => {
|
{(isActive) => {
|
||||||
switch (content) {
|
switch (content) {
|
||||||
|
|||||||
@ -451,6 +451,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
name={shouldSkipTransition ? 'none' : LAYERS_ANIMATION_NAME}
|
name={shouldSkipTransition ? 'none' : LAYERS_ANIMATION_NAME}
|
||||||
activeKey={currentScreen}
|
activeKey={currentScreen}
|
||||||
renderCount={TRANSITION_RENDER_COUNT}
|
renderCount={TRANSITION_RENDER_COUNT}
|
||||||
|
shouldWrap
|
||||||
>
|
>
|
||||||
{renderCurrentSection}
|
{renderCurrentSection}
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, memo, useCallback, useState, useRef,
|
useEffect, memo, useCallback, useState, useRef, useLayoutEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
import { addExtraClass } from '../../lib/teact/teact-dom';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { AnimationLevel, LangCode } from '../../types';
|
import type { AnimationLevel, LangCode } from '../../types';
|
||||||
@ -231,6 +232,9 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
void loadBundle(Bundles.Calls);
|
void loadBundle(Bundles.Calls);
|
||||||
}, CALL_BUNDLE_LOADING_DELAY_MS);
|
}, CALL_BUNDLE_LOADING_DELAY_MS);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { isDesktop } = useAppLayout();
|
const { isDesktop } = useAppLayout();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLeftColumnOpen && !isMiddleColumnOpen && !isDesktop) {
|
if (!isLeftColumnOpen && !isMiddleColumnOpen && !isDesktop) {
|
||||||
@ -348,6 +352,14 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [lastSyncTime, openChat]);
|
}, [lastSyncTime, openChat]);
|
||||||
|
|
||||||
|
// Restore Transition slide class after async rendering
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const container = containerRef.current!;
|
||||||
|
if (container.parentNode!.childElementCount === 1) {
|
||||||
|
addExtraClass(container, 'Transition__slide--active');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const leftColumnTransition = useShowTransition(
|
const leftColumnTransition = useShowTransition(
|
||||||
isLeftColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations, undefined, true,
|
isLeftColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations, undefined, true,
|
||||||
);
|
);
|
||||||
@ -445,7 +457,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
usePreventPinchZoomGesture(isMediaViewerOpen);
|
usePreventPinchZoomGesture(isMediaViewerOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="Main" className={className}>
|
<div ref={containerRef} id="Main" className={className}>
|
||||||
<LeftColumn />
|
<LeftColumn />
|
||||||
<MiddleColumn isMobile={isMobile} />
|
<MiddleColumn isMobile={isMobile} />
|
||||||
<RightColumn isMobile={isMobile} />
|
<RightColumn isMobile={isMobile} />
|
||||||
|
|||||||
@ -454,7 +454,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="Profile custom-scroll"
|
className="Profile custom-scroll"
|
||||||
itemSelector={buildInfiniteScrollItemSelector(resultType)}
|
itemSelector={`.shared-media-transition > .Transition__slide--active.${resultType}-list > .scroll-item`}
|
||||||
items={canRenderContent ? viewportIds : undefined}
|
items={canRenderContent ? viewportIds : undefined}
|
||||||
cacheBuster={cacheBuster}
|
cacheBuster={cacheBuster}
|
||||||
sensitiveArea={PROFILE_SENSITIVE_AREA}
|
sensitiveArea={PROFILE_SENSITIVE_AREA}
|
||||||
@ -515,15 +515,6 @@ function renderProfileInfo(chatId: string, resolvedUserId: string | undefined, i
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildInfiniteScrollItemSelector(resultType: string) {
|
|
||||||
return [
|
|
||||||
// Used on first render
|
|
||||||
`.shared-media-transition > div:only-child > .${resultType}-list > .scroll-item`,
|
|
||||||
// Used after transition
|
|
||||||
`.shared-media-transition > .Transition__slide--active > .${resultType}-list > .scroll-item`,
|
|
||||||
].join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, topicId, isMobile }): StateProps => {
|
(global, { chatId, topicId, isMobile }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|||||||
@ -30,14 +30,14 @@ export default function useTransitionFixes(
|
|||||||
if (container.style.overflowY !== 'hidden') {
|
if (container.style.overflowY !== 'hidden') {
|
||||||
const scrollBarWidth = container.offsetWidth - container.clientWidth;
|
const scrollBarWidth = container.offsetWidth - container.clientWidth;
|
||||||
container.style.overflowY = 'hidden';
|
container.style.overflowY = 'hidden';
|
||||||
container.style.marginRight = `${scrollBarWidth}px`;
|
container.style.paddingRight = `${scrollBarWidth}px`;
|
||||||
}
|
}
|
||||||
}, [containerRef]);
|
}, [containerRef]);
|
||||||
|
|
||||||
const releaseTransitionFix = useCallback(() => {
|
const releaseTransitionFix = useCallback(() => {
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
container.style.overflowY = 'scroll';
|
container.style.overflowY = 'scroll';
|
||||||
container.style.marginRight = '0';
|
container.style.paddingRight = '0';
|
||||||
}, [containerRef]);
|
}, [containerRef]);
|
||||||
|
|
||||||
return { applyTransitionFix, releaseTransitionFix };
|
return { applyTransitionFix, releaseTransitionFix };
|
||||||
|
|||||||
@ -12,8 +12,10 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.Transition__slide--active):not(.from):not(.to) {
|
&:not(.slide-optimized):not(.slide-optimized-rtl) {
|
||||||
|
> .Transition__slide:not(.Transition__slide--active):not(.from):not(.to) {
|
||||||
display: none !important; // Best performance when animating container
|
display: none !important; // Best performance when animating container
|
||||||
//transform: scale(0); // Shortest initial delay
|
//transform: scale(0); // Shortest initial delay
|
||||||
}
|
}
|
||||||
@ -36,7 +38,6 @@
|
|||||||
|
|
||||||
#root & > .Transition__slide {
|
#root & > .Transition__slide {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block !important;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
transition: transform var(--slide-transition);
|
transition: transform var(--slide-transition);
|
||||||
@ -348,7 +349,7 @@
|
|||||||
|
|
||||||
&.slide-layers {
|
&.slide-layers {
|
||||||
--background-color: var(--color-background);
|
--background-color: var(--color-background);
|
||||||
background: black;
|
background: black !important;
|
||||||
|
|
||||||
> .Transition__slide {
|
> .Transition__slide {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
@ -427,6 +428,7 @@
|
|||||||
&.push-slide.backwards {
|
&.push-slide.backwards {
|
||||||
> .to {
|
> .to {
|
||||||
transform: scale(0.7);
|
transform: scale(0.7);
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.animating {
|
&.animating {
|
||||||
|
|||||||
@ -29,18 +29,23 @@ export type TransitionProps = {
|
|||||||
shouldRestoreHeight?: boolean;
|
shouldRestoreHeight?: boolean;
|
||||||
shouldCleanup?: boolean;
|
shouldCleanup?: boolean;
|
||||||
cleanupExceptionKey?: number;
|
cleanupExceptionKey?: number;
|
||||||
|
// Used by async components which are usually remounted during first animation
|
||||||
|
shouldWrap?: boolean;
|
||||||
|
wrapExceptionKey?: number;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onStart?: NoneToVoidFunction;
|
onStart?: NoneToVoidFunction;
|
||||||
onStop?: NoneToVoidFunction;
|
onStop?: NoneToVoidFunction;
|
||||||
children: React.ReactNode | ChildrenFn;
|
children: React.ReactNode | ChildrenFn;
|
||||||
|
afterChildren?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FALLBACK_ANIMATION_END = 1000;
|
const FALLBACK_ANIMATION_END = 1000;
|
||||||
const CLASSES = {
|
const CLASSES = {
|
||||||
slide: 'Transition__slide',
|
slide: 'Transition__slide',
|
||||||
active: 'Transition__slide--active',
|
active: 'Transition__slide--active',
|
||||||
|
afterSlides: 'Transition__after-slides',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Transition: FC<TransitionProps> = ({
|
const Transition: FC<TransitionProps> = ({
|
||||||
@ -52,11 +57,14 @@ const Transition: FC<TransitionProps> = ({
|
|||||||
shouldRestoreHeight,
|
shouldRestoreHeight,
|
||||||
shouldCleanup,
|
shouldCleanup,
|
||||||
cleanupExceptionKey,
|
cleanupExceptionKey,
|
||||||
|
shouldWrap,
|
||||||
|
wrapExceptionKey,
|
||||||
id,
|
id,
|
||||||
className,
|
className,
|
||||||
onStart,
|
onStart,
|
||||||
onStop,
|
onStop,
|
||||||
children,
|
children,
|
||||||
|
afterChildren,
|
||||||
}) => {
|
}) => {
|
||||||
// No need for a container to update on change
|
// No need for a container to update on change
|
||||||
const { animationLevel } = getGlobal().settings.byKey;
|
const { animationLevel } = getGlobal().settings.byKey;
|
||||||
@ -94,7 +102,8 @@ const Transition: FC<TransitionProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
const childElements = Array.from(container.children) as HTMLElement[];
|
const childElements = (Array.from(container.children) as HTMLElement[])
|
||||||
|
.filter((el) => !el.classList.contains(CLASSES.afterSlides));
|
||||||
|
|
||||||
childElements.forEach((el) => {
|
childElements.forEach((el) => {
|
||||||
addExtraClass(el, CLASSES.slide);
|
addExtraClass(el, CLASSES.slide);
|
||||||
@ -113,7 +122,9 @@ const Transition: FC<TransitionProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const childNodes = Array.from(container.childNodes);
|
const childNodes = Array.from(container.childNodes)
|
||||||
|
.filter((el) => !(el instanceof HTMLElement && el.classList.contains(CLASSES.afterSlides)));
|
||||||
|
|
||||||
if (!activeKeyChanged || !childNodes.length) {
|
if (!activeKeyChanged || !childNodes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -271,16 +282,21 @@ const Transition: FC<TransitionProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const rendered = typeof render === 'function'
|
||||||
<div key={key} teactOrderKey={key}>{
|
? render(key === activeKey, key === prevActiveKey, activeKey)
|
||||||
typeof render === 'function'
|
: render;
|
||||||
? render(key === activeKey, key === prevActiveKey, activeKey)
|
|
||||||
: render
|
return (shouldWrap && key !== wrapExceptionKey) || asFastList
|
||||||
}
|
? <div key={key} teactOrderKey={key}>{rendered}</div>
|
||||||
</div>
|
: rendered;
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (afterChildren) {
|
||||||
|
contents.push((
|
||||||
|
<div className={CLASSES.afterSlides}>{afterChildren}</div>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user