Skip to content

Effecto de cambio de modo claro-oscuro

theme-toggle

Theme toggle effect ALL effects

/* circle */
::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: none;
z-index: -1;
}
::view-transition-new(root) {
mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><circle cx="20" cy="20" r="20" fill="white"/></svg>')
center / 0 no-repeat;
animation: scale 1s;
}
@keyframes scale {
to {
mask-size: 200vmax;
}
}
/* circle-with-blur */
::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::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="20" cy="20" r="18" fill="white" filter="url(%23blur)"/></svg>')
center / 0 no-repeat;
animation: scale 1s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: none;
z-index: -1;
}
.dark::view-transition-new(root) {
animation: scale 1s;
}
@keyframes scale {
to {
mask-size: 200vmax;
}
}
/* circle-blur-top-left */
::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::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;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 1s;
transform-origin: top left;
z-index: -1;
}
@keyframes scale {
to {
mask-size: 350vmax;
}
}
/* polygon */
::view-transition-group(root) {
animation-duration: 0.7s;
animation-timing-function: var(--expo-out);
}
::view-transition-new(root) {
animation-name: reveal-light;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: none;
z-index: -1;
}
.dark::view-transition-new(root) {
animation-name: reveal-dark;
}
@keyframes reveal-dark {
from {
clip-path: polygon(50% -71%, -50% 71%, -50% 71%, 50% -71%);
}
to {
clip-path: polygon(50% -71%, -50% 71%, 50% 171%, 171% 50%);
}
}
@keyframes reveal-light {
from {
clip-path: polygon(171% 50%, 50% 171%, 50% 171%, 171% 50%);
}
to {
clip-path: polygon(171% 50%, 50% 171%, -50% 71%, 50% -71%);
}
}
/* polygon-gradient */
::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::view-transition-new(root) {
mask: url('assets/custom-svg.svg') top left / 0 no-repeat;
mask-origin: top left;
animation: scale 1.5s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 1.5s;
z-index: -1;
transform-origin: top left;
}
@keyframes scale {
to {
mask-size: 200vmax;
}
}
/* gif-1 */
::view-transition-group(root) {
animation-timing-function: var(--expo-in);
}
::view-transition-new(root) {
mask: url('https://media.tenor.com/cyORI7kwShQAAAAi/shigure-ui-dance.gif') center / 0 no-repeat;
animation: scale 3s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 3s;
}
@keyframes scale {
0% {
mask-size: 0;
}
10% {
mask-size: 50vmax;
}
90% {
mask-size: 50vmax;
}
100% {
mask-size: 2000vmax;
}
}
/* gif-2 */
::view-transition-group(root) {
animation-timing-function: var(--expo-in);
}
::view-transition-new(root) {
mask: url('https://media.tenor.com/Jz0aSpk9VIQAAAAi/i-love-you-love.gif') center / 0 no-repeat;
animation: scale 2s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 2s;
}
@keyframes scale {
0% {
mask-size: 0;
}
10% {
mask-size: 50vmax;
}
90% {
mask-size: 50vmax;
}
100% {
mask-size: 2000vmax;
}
}
/* animation timing functions */
:root {
--expo-in: linear(
0 0%,
0.0085 31.26%,
0.0167 40.94%,
0.0289 48.86%,
0.0471 55.92%,
0.0717 61.99%,
0.1038 67.32%,
0.1443 72.07%,
0.1989 76.7%,
0.2659 80.89%,
0.3465 84.71%,
0.4419 88.22%,
0.554 91.48%,
0.6835 94.51%,
0.8316 97.34%,
1 100%
);
--expo-out: linear(
0 0%,
0.1684 2.66%,
0.3165 5.49%,
0.446 8.52%,
0.5581 11.78%,
0.6535 15.29%,
0.7341 19.11%,
0.8011 23.3%,
0.8557 27.93%,
0.8962 32.68%,
0.9283 38.01%,
0.9529 44.08%,
0.9711 51.14%,
0.9833 59.06%,
0.9915 68.74%,
1 100%
);
}
if (!document.startViewTransition) switchTheme()
document.startViewTransition(switchTheme)

Here’s some demos Now you can write your css as you wish to

following is a simple example, that uses a circular mask

::view-transition-new(root) {
mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><circle cx="20" cy="20" r="20" fill="white"/></svg>')
center / 0 no-repeat;
animation: scale 1s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: none;
z-index: -1;
}
.dark::view-transition-new(root) {
animation: scale 1s;
}
@keyframes scale {
to {
mask-size: 200vmax;
}
}

lets add a little blur to the svg

::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::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="20" cy="20" r="18" fill="white" filter="url(%23blur)"/></svg>')
center / 0 no-repeat;
animation: scale 1s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: none;
z-index: -1;
}
.dark::view-transition-new(root) {
animation: scale 1s;
}
@keyframes scale {
to {
mask-size: 200vmax;
}
}

let’s try to pivot the center of the circle to top left

::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::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;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 1s;
transform-origin: top left;
z-index: -1;
}
@keyframes scale {
to {
mask-size: 350vmax;
}
}

see this is simple, now the skylimit is your imagination

we’ve seen all the svg mask animations, but we can use clip-paths too

::view-transition-group(root) {
animation-duration: 0.7s;
animation-timing-function: var(--expo-out);
}
::view-transition-new(root) {
animation-name: reveal-light;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: none;
z-index: -1;
}
.dark::view-transition-new(root) {
animation-name: reveal-dark;
}
@keyframes reveal-dark {
from {
clip-path: polygon(50% -71%, -50% 71%, -50% 71%, 50% -71%);
}
to {
clip-path: polygon(50% -71%, -50% 71%, 50% 171%, 171% 50%);
}
}
@keyframes reveal-light {
from {
clip-path: polygon(171% 50%, 50% 171%, 50% 171%, 171% 50%);
}
to {
clip-path: polygon(171% 50%, 50% 171%, -50% 71%, 50% -71%);
}
}

the issue with using clip path is that you can’t do much with it, like adding gradient or blur. so svg should be a good choice for most cases

lets see how can we improve the clip-path animation with a custom svg with linear gradient

we can use local assets too

::view-transition-group(root) {
animation-timing-function: var(--expo-out);
}
::view-transition-new(root) {
mask: url('assets/custom-svg.svg') top left / 0 no-repeat;
mask-origin: top left;
animation: scale 1.5s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 1.5s;
z-index: -1;
transform-origin: top left;
}
@keyframes scale {
to {
mask-size: 200vmax;
}
}

here’s the cool part

you can use gifs too

::view-transition-group(root) {
animation-timing-function: var(--expo-in);
}
::view-transition-new(root) {
mask: url('https://media.tenor.com/cyORI7kwShQAAAAi/shigure-ui-dance.gif') center / 0 no-repeat;
animation: scale 3s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 3s;
}
@keyframes scale {
0% {
mask-size: 0;
}
10% {
mask-size: 50vmax;
}
90% {
mask-size: 50vmax;
}
100% {
mask-size: 2000vmax;
}
}

this one’s good 😉

::view-transition-group(root) {
animation-timing-function: var(--expo-in);
}
::view-transition-new(root) {
mask: url('https://media.tenor.com/Jz0aSpk9VIQAAAAi/i-love-you-love.gif') center / 0 no-repeat;
animation: scale 1.5s;
}
::view-transition-old(root),
.dark::view-transition-old(root) {
animation: scale 1.5s;
}
@keyframes scale {
0% {
mask-size: 0;
}
10% {
mask-size: 50vmax;
}
90% {
mask-size: 50vmax;
}
100% {
mask-size: 2000vmax;
}
}

here are the two animation timing functions i’m using for the examples

:root {
--expo-in: linear(
0 0%,
0.0085 31.26%,
0.0167 40.94%,
0.0289 48.86%,
0.0471 55.92%,
0.0717 61.99%,
0.1038 67.32%,
0.1443 72.07%,
0.1989 76.7%,
0.2659 80.89%,
0.3465 84.71%,
0.4419 88.22%,
0.554 91.48%,
0.6835 94.51%,
0.8316 97.34%,
1 100%
);
--expo-out: linear(
0 0%,
0.1684 2.66%,
0.3165 5.49%,
0.446 8.52%,
0.5581 11.78%,
0.6535 15.29%,
0.7341 19.11%,
0.8011 23.3%,
0.8557 27.93%,
0.8962 32.68%,
0.9283 38.01%,
0.9529 44.08%,
0.9711 51.14%,
0.9833 59.06%,
0.9915 68.74%,
1 100%
);
}

thats basically it. you have enough context to build cool theme transitions with view transitions api