diff --git a/src/components/ui/Transition.scss b/src/components/ui/Transition.scss index 8e941fc9d..8f1c2fa9d 100644 --- a/src/components/ui/Transition.scss +++ b/src/components/ui/Transition.scss @@ -1,9 +1,12 @@ .Transition { position: relative; + width: 100%; + height: 100%; > .Transition__slide { width: 100%; height: 100%; + animation-fill-mode: forwards !important; &.from, @@ -25,9 +28,6 @@ transition: none !important; } - /* - * slide - */ &.slide-optimized, &.slide-optimized-rtl { contain: strict; @@ -40,8 +40,9 @@ position: absolute; top: 0; left: 0; - transition: transform var(--slide-transition); transform: scale(0); + + transition: transform var(--slide-transition); } } @@ -109,10 +110,6 @@ } } - /* - * slide-vertical - */ - &.slide-vertical { > .to { transform: translateY(100%); @@ -145,10 +142,6 @@ } } - /* - * slide-vertical-fade - */ - &.slide-vertical-fade { > .to { transform: translateY(100%); @@ -181,10 +174,6 @@ } } - /* - * mv-slide - */ - &.mv-slide { > .Transition__slide > div { animation-fill-mode: forwards !important; @@ -221,19 +210,18 @@ } } - /* - * slide-fade - */ &.slide-fade { > .from { - transform-origin: left; transform: translateX(0); + transform-origin: left; + opacity: 1; } > .to { - transform-origin: left; transform: translateX(1.5rem); + transform-origin: left; + opacity: 0; } @@ -251,39 +239,40 @@ &.slide-fade.backwards { > .from { transform: translateX(0); + opacity: 1; } > .to { transform: translateX(-1.5rem); + opacity: 0; } &.animating { > .from { animation: fade-in-backwards-opacity var(--slide-transition), - slide-fade-in-backwards-move var(--slide-transition); + slide-fade-in-backwards-move var(--slide-transition); } > .to { animation: fade-out-backwards-opacity var(--slide-transition), - slide-fade-out-backwards-move var(--slide-transition); + slide-fade-out-backwards-move var(--slide-transition); } } } - /* - * zoom-fade - */ &.zoom-fade { > .from { - transform-origin: center; transform: scale(1); + transform-origin: center; + opacity: 1; } > .to { transform-origin: center; + opacity: 0; // We can omit `transform: scale(1.1);` here because `opacity` is 0. // We need to for proper position calculation in `InfiniteScroll`. @@ -320,9 +309,6 @@ } } - /* - * fade - */ &.fade { > .from { opacity: 1; @@ -343,12 +329,49 @@ } } - /* - * slide-layers - */ + &.semi-fade { + > .Transition__slide { + isolation: isolate; + } + + > .from { + opacity: 1; + } + + > .to { + opacity: 0; + } + + &.animating { + > .to { + animation: fade-in-opacity 250ms ease; + } + } + } + + &.semi-fade.backwards { + > .from { + opacity: 1; + } + + > .to { + opacity: 1; + } + + &.animating { + > .from { + animation: fade-in-backwards-opacity 250ms ease; + } + + > .to { + animation: none !important; + } + } + } &.slide-layers { --background-color: var(--color-background); + background: black !important; > .Transition__slide { @@ -373,6 +396,7 @@ &.slide-layers.backwards { > .to { transform: translateX(-20%); + opacity: 0.75; } @@ -387,18 +411,15 @@ } } - /* - * push-slide - */ - &.push-slide { > .Transition__slide { background: var(--color-background); } > .from { - transform-origin: center; transform: scale(1); + transform-origin: center; + opacity: 1; .custom-scroll { @@ -428,6 +449,7 @@ &.push-slide.backwards { > .to { transform: scale(0.7); + opacity: 0; } @@ -442,9 +464,6 @@ } } - /* - * slide-fade - */ &.reveal { > .to { clip-path: inset(0 100% 0 0); @@ -478,9 +497,6 @@ } } -/* - * common - */ @keyframes fade-in-opacity { 0% { opacity: 0; @@ -517,9 +533,6 @@ } } -/* - * slide - */ @keyframes slide-in { 0% { transform: translateX(100%); @@ -556,9 +569,6 @@ } } -/* - * slide-vertical - */ @keyframes slide-vertical-in { 0% { transform: translateY(100%); @@ -595,17 +605,15 @@ } } -/* - * slide-vertical-fade - */ - @keyframes slide-vertical-fade-in { 0% { transform: translateY(100%); + opacity: 0; } 100% { transform: translateY(0); + opacity: 1; } } @@ -613,10 +621,12 @@ @keyframes slide-vertical-fade-out { 0% { transform: translateY(0); + opacity: 1; } 100% { transform: translateY(-100%); + opacity: 0; } } @@ -624,10 +634,12 @@ @keyframes slide-vertical-fade-in-backwards { 0% { transform: translateY(0); + opacity: 1; } 100% { transform: translateY(100%); + opacity: 0; } } @@ -635,18 +647,16 @@ @keyframes slide-vertical-fade-out-backwards { 0% { transform: translateY(-100%); + opacity: 0; } 100% { transform: translateY(0); + opacity: 1; } } - -/* - * mv-slide - */ @keyframes mv-slide-in { 0% { transform: translateX(100vw); @@ -683,9 +693,6 @@ } } -/* - * slide-fade - */ @keyframes slide-fade-in-move { 0% { transform: translateX(1.5rem); @@ -722,9 +729,6 @@ } } -/* - * zoom-fade - */ @keyframes zoom-fade-in-move { 0% { transform: scale(1.1); @@ -752,16 +756,15 @@ } } -/* - * slide-layers - */ @keyframes slide-layers-out { 0% { transform: translateX(0); + opacity: 1; } 100% { transform: translateX(-20%); + opacity: calc(1 - var(--layer-blackout-opacity)); } } @@ -769,25 +772,25 @@ @keyframes slide-layers-out-backwards { 0% { transform: translateX(-20%); + opacity: calc(1 - var(--layer-blackout-opacity)); } 100% { transform: translateX(0); + opacity: 1; } } -/* - * push-slide - */ - @keyframes push-out { 0% { transform: scale(1); + opacity: 1; } 100% { transform: scale(0.7); + opacity: 0; } } @@ -795,17 +798,16 @@ @keyframes push-out-backwards { 0% { transform: scale(0.7); + opacity: 0; } 100% { transform: scale(1); + opacity: 1; } } -/* - * slide - */ @keyframes slide-in-200 { 0% { transform: translateX(200%); @@ -824,9 +826,6 @@ } } -/* - * slide - */ @keyframes reveal-in { 0% { clip-path: inset(0 100% 0 0); @@ -836,9 +835,6 @@ } } -/* - * slide - */ @keyframes reveal-in-backwards { 0% { clip-path: inset(0 0 0 0); diff --git a/src/components/ui/Transition.tsx b/src/components/ui/Transition.tsx index 7f65d9b90..7338dee88 100644 --- a/src/components/ui/Transition.tsx +++ b/src/components/ui/Transition.tsx @@ -1,5 +1,4 @@ import type { RefObject } from 'react'; -import type { FC } from '../../lib/teact/teact'; import React, { useLayoutEffect, useRef } from '../../lib/teact/teact'; import { addExtraClass, removeExtraClass, toggleExtraClass } from '../../lib/teact/teact-dom'; import { getGlobal } from '../../global'; @@ -19,10 +18,11 @@ export type ChildrenFn = (isActive: boolean, isFrom: boolean, currentKey: number export type TransitionProps = { ref?: RefObject; activeKey: number; + nextKey?: number; name: ( 'none' | 'slide' | 'slide-rtl' | 'mv-slide' | 'slide-fade' | 'zoom-fade' | 'slide-layers' - | 'fade' | 'push-slide' | 'reveal' | 'slide-optimized' | 'slide-optimized-rtl' | 'slide-vertical' - | 'slide-vertical-fade' + | 'fade' | 'push-slide' | 'reveal' | 'slide-optimized' | 'slide-optimized-rtl' | 'semi-fade' + | 'slide-vertical' | 'slide-vertical-fade' ); direction?: 'auto' | 'inverse' | 1 | -1; renderCount?: number; @@ -32,9 +32,9 @@ export type TransitionProps = { // Used by async components which are usually remounted during first animation shouldWrap?: boolean; wrapExceptionKey?: number; - isDisabled?: boolean; id?: string; className?: string; + slideClassName?: string; onStart?: NoneToVoidFunction; onStop?: NoneToVoidFunction; children: React.ReactNode | ChildrenFn; @@ -48,9 +48,10 @@ const CLASSES = { afterSlides: 'Transition__after-slides', }; -const Transition: FC = ({ +function Transition({ ref, activeKey, + nextKey, name, direction = 'auto', renderCount, @@ -61,11 +62,12 @@ const Transition: FC = ({ wrapExceptionKey, id, className, + slideClassName, onStart, onStop, children, afterChildren, -}) => { +}: TransitionProps) { // No need for a container to update on change const { animationLevel } = getGlobal().settings.byKey; const currentKeyRef = useRef(); @@ -87,6 +89,9 @@ const Transition: FC = ({ } rendersRef.current[activeKey] = children; + if (nextKey) { + rendersRef.current[nextKey] = children; + } useLayoutEffect(() => { function cleanup() { @@ -102,30 +107,38 @@ const Transition: FC = ({ } const container = containerRef.current!; - const childElements = (Array.from(container.children) as HTMLElement[]) - .filter((el) => !el.classList.contains(CLASSES.afterSlides)); - - childElements.forEach((el) => { - addExtraClass(el, CLASSES.slide); - }); - - if (childElements.length === 1 && !activeKeyChanged) { - const firstChild = childElements[0]; - - if (name.startsWith('slide-optimized')) { - firstChild.style.transition = 'none'; - firstChild.style.transform = 'translate3d(0, 0, 0)'; - } - - addExtraClass(firstChild, CLASSES.active); - - return; - } + const keys = Object.keys(rendersRef.current).map(Number); + const prevActiveIndex = renderCount ? prevActiveKey : keys.indexOf(prevActiveKey); + const activeIndex = renderCount ? activeKey : keys.indexOf(activeKey); const childNodes = Array.from(container.childNodes) .filter((el) => !(el instanceof HTMLElement && el.classList.contains(CLASSES.afterSlides))); + if (!childNodes.length) { + return; + } + + const childElements = (Array.from(container.children) as HTMLElement[]) + .filter((el) => !el.classList.contains(CLASSES.afterSlides)); + childElements.forEach((el) => { + addExtraClass(el, CLASSES.slide); + + if (slideClassName) { + addExtraClass(el, slideClassName); + } + }); + + if (!activeKeyChanged) { + if (childElements.length === 1 || (nextKey !== undefined && childElements.length === 2)) { + const firstChild = childNodes[activeIndex] as HTMLElement; + + if (name.startsWith('slide-optimized')) { + firstChild.style.transition = 'none'; + firstChild.style.transform = 'translate3d(0, 0, 0)'; + } + + addExtraClass(firstChild, CLASSES.active); + } - if (!activeKeyChanged || !childNodes.length) { return; } @@ -137,10 +150,6 @@ const Transition: FC = ({ || (direction === 'inverse' && prevActiveKey < activeKey) ); - const keys = Object.keys(rendersRef.current).map(Number); - const prevActiveIndex = renderCount ? prevActiveKey : keys.indexOf(prevActiveKey); - const activeIndex = renderCount ? activeKey : keys.indexOf(activeKey); - if (name === 'slide-optimized' || name === 'slide-optimized-rtl') { performSlideOptimized( animationLevel, @@ -243,6 +252,7 @@ const Transition: FC = ({ }); }, [ activeKey, + nextKey, prevActiveKey, activeKeyChanged, direction, @@ -252,6 +262,7 @@ const Transition: FC = ({ renderCount, shouldRestoreHeight, shouldCleanup, + slideClassName, cleanupExceptionKey, animationLevel, forceUpdate, @@ -307,7 +318,7 @@ const Transition: FC = ({ {contents} ); -}; +} export default Transition; diff --git a/src/lib/teact/teact-dom.ts b/src/lib/teact/teact-dom.ts index abd64b14e..a869c9601 100644 --- a/src/lib/teact/teact-dom.ts +++ b/src/lib/teact/teact-dom.ts @@ -700,7 +700,18 @@ function updateClassName(element: HTMLElement, value: string) { element.className = extraArray.join(' '); } -export function addExtraClass(element: HTMLElement, className: string) { +export function addExtraClass(element: HTMLElement, className: string, forceSingle = false) { + if (!forceSingle) { + const classNames = className.split(' '); + if (className.length > 1) { + classNames.forEach((cn) => { + addExtraClass(element, cn, true); + }); + + return; + } + } + element.classList.add(className); const classList = extraClasses.get(element); @@ -711,7 +722,18 @@ export function addExtraClass(element: HTMLElement, className: string) { } } -export function removeExtraClass(element: HTMLElement, className: string) { +export function removeExtraClass(element: HTMLElement, className: string, forceSingle = false) { + if (!forceSingle) { + const classNames = className.split(' '); + if (className.length > 1) { + classNames.forEach((cn) => { + removeExtraClass(element, cn, true); + }); + + return; + } + } + element.classList.remove(className); const classList = extraClasses.get(element); @@ -724,7 +746,18 @@ export function removeExtraClass(element: HTMLElement, className: string) { } } -export function toggleExtraClass(element: HTMLElement, className: string, force?: boolean) { +export function toggleExtraClass(element: HTMLElement, className: string, force?: boolean, forceSingle = false) { + if (!forceSingle) { + const classNames = className.split(' '); + if (className.length > 1) { + classNames.forEach((cn) => { + toggleExtraClass(element, cn, force, true); + }); + + return; + } + } + element.classList.toggle(className, force); if (element.classList.contains(className)) {