Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/overlay-backdrop-compositor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"reshaped": patch
---

Improved the `Overlay` animation performance by rendering its backdrop as a separate layer with a static color and transitioning only its `opacity`. Previously the full-viewport root transitioned `background-color` on open and close and during the `Modal` drag-to-close gesture, which forced the browser to repaint the whole viewport on every frame on the main thread. The transition now runs entirely on the compositor thread while keeping the same visual result.
35 changes: 28 additions & 7 deletions packages/reshaped/src/components/Overlay/Overlay.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/* Add an offset in case overlay is rendered inside a scrolled container */
--rs-overlay-offset-y: 0px;
--rs-overlay-offset-x: 0px;
--rs-overlay-opacity: 0;

position: fixed;
overflow-x: clip;
Expand All @@ -15,7 +14,6 @@
width: 100%;
z-index: var(--rs-z-index-fixed);
color: var(--rs-color-white);
background-color: rgb(from var(--rs-color-black) r g b / 0%);
opacity: 0;
outline: none;
isolation: isolate;
Expand All @@ -25,6 +23,17 @@
display: table;
height: 100%;
width: 100%;
position: relative;
}

/* Setting opacity separately for swipe updates directly in the dom */
.backdrop {
position: absolute;
inset: 0;
z-index: -1;
background-color: rgb(from var(--rs-color-black) r g b / 70%);
opacity: 0;
will-change: opacity;
}

.inner {
Expand All @@ -40,15 +49,20 @@
}

.root.--visible {
--rs-overlay-opacity: 1;

background-color: rgb(from var(--rs-color-black) r g b / calc(var(--rs-overlay-opacity) * 0.7));
opacity: 1;

& .backdrop {
opacity: 1;
}
}

.root.--click-through {
color: inherit;
pointer-events: none;

& .backdrop {
opacity: 0;
}
}

.root.--blurred {
Expand All @@ -74,11 +88,18 @@
}

.root.--animated {
transition: var(--rs-duration-medium) var(--rs-easing-accelerate);
transition-property: background-color, opacity;
transition: opacity var(--rs-duration-medium) var(--rs-easing-accelerate);

& .backdrop {
transition: opacity var(--rs-duration-medium) var(--rs-easing-accelerate);
}

&.--visible {
transition-timing-function: var(--rs-easing-decelerate);

& .backdrop {
transition-timing-function: var(--rs-easing-decelerate);
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/reshaped/src/components/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const Overlay: React.FC<T.Props> = (props) => {
const [animated, setAnimated] = React.useState(false);
const [offset, setOffset] = React.useState([0, 0]);
const rootRef = React.useRef<HTMLDivElement>(null);
const backdropRef = React.useRef<HTMLDivElement>(null);
const scopeRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
const { lockScroll, unlockScroll } = useScrollLock({ containerRef });
Expand Down Expand Up @@ -183,7 +184,8 @@ const Overlay: React.FC<T.Props> = (props) => {
() => ({
setOpacity: (value: number) => {
if (transparent) return;
rootRef.current?.style.setProperty("--rs-overlay-opacity", `${value}`);
// Applied directly to the backdrop element to keep the style recalculation scoped to it
backdropRef.current?.style.setProperty("opacity", `${value}`);
},
}),
[transparent]
Expand All @@ -205,7 +207,6 @@ const Overlay: React.FC<T.Props> = (props) => {
{
"--rs-overlay-offset-x": containerRef ? `${offset[0]}px` : undefined,
"--rs-overlay-offset-y": containerRef ? `${offset[1]}px` : undefined,
"--rs-overlay-opacity": transparent ? 0 : undefined,
} as React.CSSProperties
}
// oxlint-disable-next-line jsx_a11y/prefer-tag-over-role
Expand All @@ -217,6 +218,7 @@ const Overlay: React.FC<T.Props> = (props) => {
onTransitionEnd={handleTransitionEnd}
>
<div className={s.wrapper}>
<div className={s.backdrop} ref={backdropRef} />
<div className={s.inner}>
<div className={s.content} ref={contentRef}>
{typeof children === "function" ? children({ active: visible }) : children}
Expand Down
Loading