[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,
}
const TRANSITION_RENDER_COUNT = Object.keys(AppScreens).length / 2;
const INACTIVE_PAGE_TITLE = `${PAGE_TITLE} ${INACTIVE_MARKER}`;
const App: FC<StateProps> = ({
@ -204,7 +205,7 @@ const App: FC<StateProps> = ({
}, [theme]);
return (
<UiLoader key="Loader" page={page} isMobile={isMobile}>
<UiLoader page={page} isMobile={isMobile}>
<Transition
name="fade"
activeKey={activeKey}
@ -213,6 +214,7 @@ const App: FC<StateProps> = ({
'full-height',
(activeKey === AppScreens.auth || prevActiveKey === AppScreens.auth) && 'is-auth',
)}
renderCount={TRANSITION_RENDER_COUNT}
>
{renderContent}
</Transition>

View File

@ -484,19 +484,19 @@ const LeftColumn: FC<StateProps> = ({
activeKey={contentType}
shouldCleanup
cleanupExceptionKey={ContentType.Main}
shouldWrap
wrapExceptionKey={ContentType.Main}
id="LeftColumn"
>
{(isActive) => (
<>
{renderContent(isActive)}
<div
className="resize-handle"
onMouseDown={initResize}
onMouseUp={handleMouseUp}
onDoubleClick={resetResize}
/>
</>
afterChildren={(
<div
className="resize-handle"
onMouseDown={initResize}
onMouseUp={handleMouseUp}
onDoubleClick={resetResize}
/>
)}
>
{renderContent}
</Transition>
);
};

View File

@ -4,7 +4,6 @@
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 1;
> .Transition {
flex: 1;

View File

@ -172,6 +172,8 @@ const LeftMain: FC<OwnProps> = ({
activeKey={content}
shouldCleanup
cleanupExceptionKey={LeftColumnContent.ChatList}
shouldWrap
wrapExceptionKey={LeftColumnContent.ChatList}
>
{(isActive) => {
switch (content) {

View File

@ -451,6 +451,7 @@ const Settings: FC<OwnProps> = ({
name={shouldSkipTransition ? 'none' : LAYERS_ANIMATION_NAME}
activeKey={currentScreen}
renderCount={TRANSITION_RENDER_COUNT}
shouldWrap
>
{renderCurrentSection}
</Transition>

View File

@ -1,7 +1,8 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useEffect, memo, useCallback, useState, useRef,
useEffect, memo, useCallback, useState, useRef, useLayoutEffect,
} from '../../lib/teact/teact';
import { addExtraClass } from '../../lib/teact/teact-dom';
import { getActions, getGlobal, withGlobal } from '../../global';
import type { AnimationLevel, LangCode } from '../../types';
@ -231,6 +232,9 @@ const Main: FC<OwnProps & StateProps> = ({
void loadBundle(Bundles.Calls);
}, CALL_BUNDLE_LOADING_DELAY_MS);
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const { isDesktop } = useAppLayout();
useEffect(() => {
if (!isLeftColumnOpen && !isMiddleColumnOpen && !isDesktop) {
@ -348,6 +352,14 @@ const Main: FC<OwnProps & StateProps> = ({
}
}, [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(
isLeftColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations, undefined, true,
);
@ -445,7 +457,7 @@ const Main: FC<OwnProps & StateProps> = ({
usePreventPinchZoomGesture(isMediaViewerOpen);
return (
<div id="Main" className={className}>
<div ref={containerRef} id="Main" className={className}>
<LeftColumn />
<MiddleColumn isMobile={isMobile} />
<RightColumn isMobile={isMobile} />

View File

@ -454,7 +454,7 @@ const Profile: FC<OwnProps & StateProps> = ({
<InfiniteScroll
ref={containerRef}
className="Profile custom-scroll"
itemSelector={buildInfiniteScrollItemSelector(resultType)}
itemSelector={`.shared-media-transition > .Transition__slide--active.${resultType}-list > .scroll-item`}
items={canRenderContent ? viewportIds : undefined}
cacheBuster={cacheBuster}
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>(
(global, { chatId, topicId, isMobile }): StateProps => {
const chat = selectChat(global, chatId);

View File

@ -30,14 +30,14 @@ export default function useTransitionFixes(
if (container.style.overflowY !== 'hidden') {
const scrollBarWidth = container.offsetWidth - container.clientWidth;
container.style.overflowY = 'hidden';
container.style.marginRight = `${scrollBarWidth}px`;
container.style.paddingRight = `${scrollBarWidth}px`;
}
}, [containerRef]);
const releaseTransitionFix = useCallback(() => {
const container = containerRef.current!;
container.style.overflowY = 'scroll';
container.style.marginRight = '0';
container.style.paddingRight = '0';
}, [containerRef]);
return { applyTransitionFix, releaseTransitionFix };

View File

@ -12,8 +12,10 @@
top: 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
//transform: scale(0); // Shortest initial delay
}
@ -36,7 +38,6 @@
#root & > .Transition__slide {
position: absolute;
display: block !important;
top: 0;
left: 0;
transition: transform var(--slide-transition);
@ -348,7 +349,7 @@
&.slide-layers {
--background-color: var(--color-background);
background: black;
background: black !important;
> .Transition__slide {
background: var(--background-color);
@ -427,6 +428,7 @@
&.push-slide.backwards {
> .to {
transform: scale(0.7);
opacity: 0;
}
&.animating {

View File

@ -29,18 +29,23 @@ export type TransitionProps = {
shouldRestoreHeight?: boolean;
shouldCleanup?: boolean;
cleanupExceptionKey?: number;
// Used by async components which are usually remounted during first animation
shouldWrap?: boolean;
wrapExceptionKey?: number;
isDisabled?: boolean;
id?: string;
className?: string;
onStart?: NoneToVoidFunction;
onStop?: NoneToVoidFunction;
children: React.ReactNode | ChildrenFn;
afterChildren?: React.ReactNode;
};
const FALLBACK_ANIMATION_END = 1000;
const CLASSES = {
slide: 'Transition__slide',
active: 'Transition__slide--active',
afterSlides: 'Transition__after-slides',
};
const Transition: FC<TransitionProps> = ({
@ -52,11 +57,14 @@ const Transition: FC<TransitionProps> = ({
shouldRestoreHeight,
shouldCleanup,
cleanupExceptionKey,
shouldWrap,
wrapExceptionKey,
id,
className,
onStart,
onStop,
children,
afterChildren,
}) => {
// No need for a container to update on change
const { animationLevel } = getGlobal().settings.byKey;
@ -94,7 +102,8 @@ const Transition: FC<TransitionProps> = ({
}
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) => {
addExtraClass(el, CLASSES.slide);
@ -113,7 +122,9 @@ const Transition: FC<TransitionProps> = ({
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) {
return;
}
@ -271,16 +282,21 @@ const Transition: FC<TransitionProps> = ({
return undefined;
}
return (
<div key={key} teactOrderKey={key}>{
typeof render === 'function'
? render(key === activeKey, key === prevActiveKey, activeKey)
: render
}
</div>
);
const rendered = typeof render === 'function'
? render(key === activeKey, key === prevActiveKey, activeKey)
: render;
return (shouldWrap && key !== wrapExceptionKey) || asFastList
? <div key={key} teactOrderKey={key}>{rendered}</div>
: rendered;
});
if (afterChildren) {
contents.push((
<div className={CLASSES.afterSlides}>{afterChildren}</div>
));
}
return (
<div
ref={containerRef}