theme-toggle-effect icon indicating copy to clipboard operation
theme-toggle-effect copied to clipboard

Chrome 140: Flash of old theme after transition completes due to finished promise timing change

Open NoorBayari opened this issue 4 months ago • 0 comments

Description

After Chrome 140 was released (September 2, 2025), the theme toggle animation shows a brief flash of the old theme after the transition animation completes. This only happens in Chrome 140+.

Steps to Reproduce

  1. Use Chrome 140 or later
  2. Visit https://theme-toggle.rdsx.dev/
  3. Toggle between light and dark themes
  4. Observe a quick flash of the previous theme after the animation completes

Expected Behavior

The theme transition should complete smoothly without any flash of the old theme.

Actual Behavior

There's a brief but noticeable flash of the old theme right after the mask animation finishes.

Root Cause

Chrome 140 introduced a view transition finished promise timing change [official release notes]

"The current finished promise timing happens within the rendering lifecycle steps. This means that code that runs as a result of promise resolution happens after the visual frame that removes the view transition has been produced. This can cause a flicker at the end of the animation if the script moves styles to preserve a visually similar state. This change resolves the issue by moving the view transition cleanup steps to run asynchronously after the lifecycle is completed."

The mask animation reverts to its initial state (mask-size: 0) before the view transition pseudo-elements are cleaned up, causing the flash.

Solution

Add animation-fill-mode: forwards to maintain the mask's final state until cleanup completes:

::view-transition-new(root) {
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><filter id="blur"><feGaussianBlur stdDeviation="2"/></filter></defs><circle cx="0" cy="0" r="18" fill="white" filter="url(%23blur)"/></svg>')
    top left / 0 no-repeat;
  mask-origin: content-box;
  animation: scale 1s;
  transform-origin: top left;
  animation-fill-mode: forwards; /* ← Add this line */
}

Why This Fix Works

Without animation-fill-mode: forwards, the mask animation completes and reverts to its default state. With Chrome 140's new asynchronous cleanup timing, there's now a visible gap between when the animation ends and when the pseudo-elements are removed, causing the flash. The forwards value ensures the mask stays at its final expanded state until cleanup is complete.

Browser Compatibility

This fix is safe and doesn't break any browsers:

  • ✅ Chrome 111+ (View Transitions support)
  • ✅ Safari 18+ (View Transitions support)
  • animation-fill-mode has been supported since 2015 in all major browsers

NoorBayari avatar Oct 01 '25 15:10 NoorBayari