Repository navigation
Overlay: animate backdrop opacity instead of background-color - #667
Merged
Merged
Conversation
The overlay root is a position: fixed element covering the viewport and transitioned background-color together with opacity on open/close, and updated background-color on every frame of the Modal drag-to-close gesture. background-color changes are painted on the main thread, so every frame repainted the whole viewport. Move the backdrop to a ::before layer with a static 70% black background whose opacity follows --rs-overlay-opacity. Opacity is a compositor-only property, so the transition and the drag updates no longer trigger paint. The pseudo element is attached to the wrapper (which grows with the content) so it still covers the whole scrollable area when the overlay has overflow: auto. Both the root and the backdrop keep the same duration and easing, so the resulting fade curve is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAi9QQVSFuJ7Gkf8SUTYJq
size-limit report 📦
|
Driving the backdrop opacity through an inherited custom property set on the root made Chromium recalculate style for the whole overlay subtree (the entire modal content) on every drag frame. The backdrop is now a real element and setOpacity writes its opacity directly, so the recalculation stays scoped to that single element. The internal --rs-overlay-opacity custom property is removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAi9QQVSFuJ7Gkf8SUTYJq
This was referenced Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Following the guidance in The Browser's Main Thread Is Expensive: only
transformandopacitycan be animated on the compositor thread, everything else forces style/layout/paint work on the main thread every frame.The
Overlayroot is aposition: fixedelement covering the whole viewport. It transitionedbackground-color(0% → 70% black) together withopacityon open/close, andModalupdated thatbackground-coloron every frame of the drag-to-close gesture through the inherited--rs-overlay-opacitycustom property. Every one of those frames repainted the full viewport on the main thread, and the custom property change on the root also forced a style recalculation of the entire overlay subtree (the whole modal content).This PR renders the backdrop as a dedicated
.backdropelement with a static 70% black background and animates only itsopacity:.backdroptransitionsopacitywith the same duration/easing as the root, so the resulting fade curve (0.7 · t²) is unchanged and runs on the compositor.instanceRef.setOpacity()now writesstyle.opacitydirectly on the backdrop element, so the update is scoped to that single element instead of invalidating style for every descendant. Measured in Chromium: updating an inherited custom property on the root of a 6000-node subtree costs ~10ms of style recalc per update, versus ~0.01ms for a direct opacity write on one element.display: tablebox that grows with the content) so it keeps covering the whole scrollable area whenoverflow="auto"is used by centered modals.transparentandblurredvariants keep working:transparentsets the backdrop opacity to 0 via the existing--click-throughclass,backdrop-filterstays on the root.--rs-overlay-opacitycustom property is removed; the publicinstanceRef.setOpacity()API is unchanged.Related Issue
N/A
Screenshots / Recordings
Verified in headless Chromium against Storybook: the blurred, transparent, and tall centered (scrolled
overflow: auto) modal states are pixel-identical tomainat rest, the backdrop still covers the viewport when the overlay is scrolled to the bottom, and a simulated touch drag on a bottom modal dims the backdrop (opacity 0.68 at 88px of drag) and closes on release.Notes for Reviewers
will-change: opacityon the backdrop keeps it promoted between drag updates sosetOpacity()calls don't trigger repaints; the layer only exists while an overlay is rendered.🤖 Generated with Claude Code
https://claude.ai/code/session_01EAi9QQVSFuJ7Gkf8SUTYJq