[Perf] Transition: Get rid of redundant slide wrappers

This commit is contained in:
Alexander Zinchuk 2023-04-15 13:50:26 +02:00
parent 982ba8c3a7
commit ecf4ffb7ae
10 changed files with 65 additions and 40 deletions

View File

@ -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>

View File

@ -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>
); );
}; };

View File

@ -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;

View File

@ -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) {

View File

@ -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>

View File

@ -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} />

View File

@ -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);

View File

@ -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 };

View File

@ -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 {

View File

@ -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}