Changing your website’s theme from light to dark (or vice versa) is already a fun interaction, but you can make it even more memorable by adding a custom GIF animation during the transition. This guide will show you how to create that effect using CSS View Transitions API and a custom mask animation.
The result: when your visitor clicks the theme toggle button, a GIF will play as a mask, revealing the new theme in a smooth and stylish way.
How the Effect Works
The magic happens in two parts:
- CSS handles the mask animation. The mask is the shape through which the new theme appears. In our case, that mask is your chosen GIF.
- JavaScript intercepts the toggle click and wraps the theme change inside the
document.startViewTransition()
method, making the animation possible.
This technique respects user accessibility preferences. If someone has “reduced motion” enabled, the animation duration becomes almost instant to avoid discomfort.
The Code
Below is the full code you can drop directly into your HTML file.
1. HTML Structure
Make sure you have a toggle button for switching themes. For example:
<button class="cs-header__scheme-toggle" aria-label="Toggle Theme">
Toggle Theme
</button>
2. CSS Styling and Animation
<style>
:root {
--expo-in: cubic-bezier(0.7, 0, 0.84, 0);
--anim-seconds: 3s; /* Match with JS ANIM_SECONDS */
}
/* Main transition animation */
::view-transition-group(root) {
animation-timing-function: var(--expo-in);
}
::view-transition-new(root) {
mask: url('your-gif-url') center / 0 no-repeat;
animation: vt-mask-scale var(--anim-seconds) both;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: vt-mask-scale var(--anim-seconds) both;
}
@keyframes vt-mask-scale {
0% { mask-size: 0; }
10% { mask-size: 50vmax; }
90% { mask-size: 50vmax; }
100% { mask-size: 2000vmax; }
}
/* Accessibility: Reduced Motion */
@media (prefers-reduced-motion: reduce) {
::view-transition-new(root),
::view-transition-old(root),
.dark::view-transition-old(root) {
animation-duration: 0.01s;
}
}
</style>
3. JavaScript Logic
<script>
(function () {
"use strict";
const TOGGLE_SELECTOR = ".cs-header__scheme-toggle";
const GIF_URL = "your-gif-url";
const ANIM_SECONDS = 3;
/* Dynamically set the GIF in the CSS mask */
const styleEl = document.createElement("style");
styleEl.textContent =
"::view-transition-new(root){mask:url('" + GIF_URL + "') center/0 no-repeat;animation:vt-mask-scale " + ANIM_SECONDS + "s both}";
document.head.appendChild(styleEl);
/* Skip if View Transitions are not supported */
if (!document.startViewTransition) return;
let wrapping = false;
/* Intercept clicks on the toggle */
document.addEventListener("click", function (e) {
const btn = e.target.closest(TOGGLE_SELECTOR);
if (!btn) return;
if (wrapping || e.__vtWrapped) return;
e.preventDefault();
e.stopPropagation();
wrapping = true;
document.startViewTransition(() => {
const evt = new MouseEvent("click", { bubbles: true, cancelable: true, composed: true });
Object.defineProperty(evt, "__vtWrapped", { value: true });
btn.dispatchEvent(evt);
}).finished.finally(() => {
wrapping = false;
});
}, true);
})();
</script>
Customization Tips
- Change the GIF: Replace the
GIF_URL
in the JavaScript with your own GIF link. - Adjust Animation Speed: Change
--anim-seconds
in CSS andANIM_SECONDS
in JS to control how fast the animation plays. - Mask Shape: Currently, the mask expands in a circular fashion. You can experiment with other shapes or even SVG masks for unique effects.
Browser Support
The View Transitions API is currently supported in modern Chromium-based browsers like Chrome and Edge. Other browsers will simply skip the animation, and the theme will change instantly.
With this setup, your theme toggle button will no longer be a simple color flip. It becomes an eye-catching feature that makes your website feel more interactive and memorable.
If you found this guide helpful, follow ctrlatimran.com for more tutorials, tips, and creative coding ideas. You can also contact me directly if you need help implementing this or customizing it for your site.