├── .github └── assets │ └── vta.gif ├── README.md ├── animation-registry.js ├── animations.txt ├── assets ├── custom-svg.svg ├── favicon.svg └── og.png ├── index.html ├── prism.css ├── script.js └── style.css /.github/assets/vta.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudrodip/theme-toggle-effect/df45f3602d53572b2418735bf148c4c7821e2e43/.github/assets/vta.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Theme toggle effect 2 | 3 | Here's how we can create theme toggle effect using view transitions api 4 | 5 | This is literally the two lines of js you need 6 | 7 | ```js 8 | if (!document.startViewTransition) switchTheme() 9 | document.startViewTransition(switchTheme); 10 | ``` 11 | 12 | Then you can write your css as you wish to 13 | 14 | For example 15 | 16 | ```css 17 | ::view-transition-group(root) { 18 | animation-timing-function: var(--expo-out); 19 | } 20 | 21 | ::view-transition-new(root) { 22 | mask: url('data:image/svg+xml,') center / 0 no-repeat; 23 | animation: scale 1s; 24 | } 25 | 26 | ::view-transition-old(root), 27 | .dark::view-transition-old(root) { 28 | animation: none; 29 | z-index: -1; 30 | } 31 | .dark::view-transition-new(root) { 32 | animation: scale 1s; 33 | } 34 | 35 | @keyframes scale { 36 | to { 37 | mask-size: 200vmax; 38 | } 39 | } 40 | ``` 41 | 42 | This will create a nice circular transition effect when you switch themes. 43 |  44 | 45 | For more examples, visit [theme-toggle.rdsx.dev](https://theme-toggle.rdsx.dev) 46 | 47 | Don't forget to star the repo if you like it 48 | 49 | Follow me on [x (twitter)](https://x.com/rds_agi) & [github](https://github.com/rudrodip) 50 | -------------------------------------------------------------------------------- /animation-registry.js: -------------------------------------------------------------------------------- 1 | const ANIMATIONS = [ 2 | { 3 | name: "circle", 4 | css: ` 5 | ::view-transition-group(root) { 6 | animation-timing-function: var(--expo-out); 7 | } 8 | ::view-transition-old(root), .dark::view-transition-old(root) { 9 | animation: none; 10 | z-index: -1; 11 | } 12 | ::view-transition-new(root) { 13 | mask: url('data:image/svg+xml,') center / 0 no-repeat; 14 | animation: scale 1s; 15 | } 16 | @keyframes scale { 17 | to { 18 | mask-size: 200vmax; 19 | } 20 | } 21 | `, 22 | }, 23 | { 24 | name: "circle-with-blur", 25 | css: ` 26 | ::view-transition-group(root) { 27 | animation-timing-function: var(--expo-out); 28 | } 29 | ::view-transition-new(root) { 30 | mask: url('data:image/svg+xml,') center / 0 no-repeat; 31 | animation: scale 1s; 32 | } 33 | ::view-transition-old(root), 34 | .dark::view-transition-old(root) { 35 | animation: none; 36 | z-index: -1; 37 | } 38 | .dark::view-transition-new(root) { 39 | animation: scale 1s; 40 | } 41 | @keyframes scale { 42 | to { 43 | mask-size: 200vmax; 44 | } 45 | } 46 | `, 47 | }, 48 | { 49 | name: "circle-blur-top-left", 50 | css: ` 51 | ::view-transition-group(root) { 52 | animation-timing-function: var(--expo-out); 53 | } 54 | ::view-transition-new(root) { 55 | mask: url('data:image/svg+xml,') top left / 0 no-repeat; 56 | mask-origin: content-box; 57 | animation: scale 1s; 58 | transform-origin: top left; 59 | } 60 | ::view-transition-old(root), 61 | .dark::view-transition-old(root) { 62 | animation: scale 1s; 63 | transform-origin: top left; 64 | z-index: -1; 65 | } 66 | @keyframes scale { 67 | to { 68 | mask-size: 350vmax; 69 | } 70 | } 71 | `, 72 | }, 73 | { 74 | name: "polygon", 75 | css: ` 76 | ::view-transition-group(root) { 77 | animation-duration: 0.7s; 78 | animation-timing-function: var(--expo-out); 79 | } 80 | ::view-transition-new(root) { 81 | animation-name: reveal-light; 82 | } 83 | ::view-transition-old(root), 84 | .dark::view-transition-old(root) { 85 | animation: none; 86 | z-index: -1; 87 | } 88 | .dark::view-transition-new(root) { 89 | animation-name: reveal-dark; 90 | } 91 | @keyframes reveal-dark { 92 | from { 93 | clip-path: polygon(50% -71%, -50% 71%, -50% 71%, 50% -71%); 94 | } 95 | to { 96 | clip-path: polygon(50% -71%, -50% 71%, 50% 171%, 171% 50%); 97 | } 98 | } 99 | @keyframes reveal-light { 100 | from { 101 | clip-path: polygon(171% 50%, 50% 171%, 50% 171%, 171% 50%); 102 | } 103 | to { 104 | clip-path: polygon(171% 50%, 50% 171%, -50% 71%, 50% -71%); 105 | } 106 | } 107 | `, 108 | }, 109 | { 110 | name: "polygon-gradient", 111 | css: ` 112 | ::view-transition-group(root) { 113 | animation-timing-function: var(--expo-out); 114 | } 115 | ::view-transition-new(root) { 116 | mask: url('assets/custom-svg.svg') top left / 0 no-repeat; 117 | mask-origin: top left; 118 | animation: scale 1.5s; 119 | } 120 | ::view-transition-old(root), 121 | .dark::view-transition-old(root) { 122 | animation: scale 1.5s; 123 | z-index: -1; 124 | transform-origin: top left; 125 | } 126 | @keyframes scale { 127 | to { 128 | mask-size: 200vmax; 129 | } 130 | } 131 | `, 132 | }, 133 | { 134 | name: "gif-1", 135 | css: ` 136 | ::view-transition-group(root) { 137 | animation-timing-function: var(--expo-in); 138 | } 139 | ::view-transition-new(root) { 140 | mask: url('https://media.tenor.com/cyORI7kwShQAAAAi/shigure-ui-dance.gif') center / 0 no-repeat; 141 | animation: scale 3s; 142 | } 143 | ::view-transition-old(root), 144 | .dark::view-transition-old(root) { 145 | animation: scale 3s; 146 | } 147 | @keyframes scale { 148 | 0% { 149 | mask-size: 0; 150 | } 151 | 10% { 152 | mask-size: 50vmax; 153 | } 154 | 90% { 155 | mask-size: 50vmax; 156 | } 157 | 100% { 158 | mask-size: 2000vmax; 159 | } 160 | } 161 | `, 162 | }, 163 | { 164 | name: "gif-2", 165 | css: ` 166 | ::view-transition-group(root) { 167 | animation-timing-function: var(--expo-in); 168 | } 169 | ::view-transition-new(root) { 170 | mask: url('https://media.tenor.com/Jz0aSpk9VIQAAAAi/i-love-you-love.gif') center / 0 no-repeat; 171 | animation: scale 2s; 172 | } 173 | ::view-transition-old(root), 174 | .dark::view-transition-old(root) { 175 | animation: scale 2s; 176 | } 177 | @keyframes scale { 178 | 0% { 179 | mask-size: 0; 180 | } 181 | 10% { 182 | mask-size: 50vmax; 183 | } 184 | 90% { 185 | mask-size: 50vmax; 186 | } 187 | 100% { 188 | mask-size: 2000vmax; 189 | } 190 | } 191 | `, 192 | }, 193 | ]; -------------------------------------------------------------------------------- /animations.txt: -------------------------------------------------------------------------------- 1 | /* circle */ 2 | 3 | ::view-transition-group(root) { 4 | animation-timing-function: var(--expo-out); 5 | } 6 | ::view-transition-old(root), .dark::view-transition-old(root) { 7 | animation: none; 8 | z-index: -1; 9 | } 10 | ::view-transition-new(root) { 11 | mask: url('data:image/svg+xml,') center / 0 no-repeat; 12 | animation: scale 1s; 13 | } 14 | @keyframes scale { 15 | to { 16 | mask-size: 200vmax; 17 | } 18 | } 19 | 20 | 21 | /* circle-with-blur */ 22 | 23 | ::view-transition-group(root) { 24 | animation-timing-function: var(--expo-out); 25 | } 26 | ::view-transition-new(root) { 27 | mask: url('data:image/svg+xml,') center / 0 no-repeat; 28 | animation: scale 1s; 29 | } 30 | ::view-transition-old(root), 31 | .dark::view-transition-old(root) { 32 | animation: none; 33 | z-index: -1; 34 | } 35 | .dark::view-transition-new(root) { 36 | animation: scale 1s; 37 | } 38 | @keyframes scale { 39 | to { 40 | mask-size: 200vmax; 41 | } 42 | } 43 | 44 | 45 | /* circle-blur-top-left */ 46 | 47 | ::view-transition-group(root) { 48 | animation-timing-function: var(--expo-out); 49 | } 50 | ::view-transition-new(root) { 51 | mask: url('data:image/svg+xml,') top left / 0 no-repeat; 52 | mask-origin: content-box; 53 | animation: scale 1s; 54 | transform-origin: top left; 55 | } 56 | ::view-transition-old(root), 57 | .dark::view-transition-old(root) { 58 | animation: scale 1s; 59 | transform-origin: top left; 60 | z-index: -1; 61 | } 62 | @keyframes scale { 63 | to { 64 | mask-size: 350vmax; 65 | } 66 | } 67 | 68 | 69 | /* polygon */ 70 | 71 | ::view-transition-group(root) { 72 | animation-duration: 0.7s; 73 | animation-timing-function: var(--expo-out); 74 | } 75 | ::view-transition-new(root) { 76 | animation-name: reveal-light; 77 | } 78 | ::view-transition-old(root), 79 | .dark::view-transition-old(root) { 80 | animation: none; 81 | z-index: -1; 82 | } 83 | .dark::view-transition-new(root) { 84 | animation-name: reveal-dark; 85 | } 86 | @keyframes reveal-dark { 87 | from { 88 | clip-path: polygon(50% -71%, -50% 71%, -50% 71%, 50% -71%); 89 | } 90 | to { 91 | clip-path: polygon(50% -71%, -50% 71%, 50% 171%, 171% 50%); 92 | } 93 | } 94 | @keyframes reveal-light { 95 | from { 96 | clip-path: polygon(171% 50%, 50% 171%, 50% 171%, 171% 50%); 97 | } 98 | to { 99 | clip-path: polygon(171% 50%, 50% 171%, -50% 71%, 50% -71%); 100 | } 101 | } 102 | 103 | 104 | /* polygon-gradient */ 105 | 106 | ::view-transition-group(root) { 107 | animation-timing-function: var(--expo-out); 108 | } 109 | ::view-transition-new(root) { 110 | mask: url('assets/custom-svg.svg') top left / 0 no-repeat; 111 | mask-origin: top left; 112 | animation: scale 1.5s; 113 | } 114 | ::view-transition-old(root), 115 | .dark::view-transition-old(root) { 116 | animation: scale 1.5s; 117 | z-index: -1; 118 | transform-origin: top left; 119 | } 120 | @keyframes scale { 121 | to { 122 | mask-size: 200vmax; 123 | } 124 | } 125 | 126 | 127 | /* gif-1 */ 128 | 129 | ::view-transition-group(root) { 130 | animation-timing-function: var(--expo-in); 131 | } 132 | ::view-transition-new(root) { 133 | mask: url('https://media.tenor.com/cyORI7kwShQAAAAi/shigure-ui-dance.gif') center / 0 no-repeat; 134 | animation: scale 3s; 135 | } 136 | ::view-transition-old(root), 137 | .dark::view-transition-old(root) { 138 | animation: scale 3s; 139 | } 140 | @keyframes scale { 141 | 0% { 142 | mask-size: 0; 143 | } 144 | 10% { 145 | mask-size: 50vmax; 146 | } 147 | 90% { 148 | mask-size: 50vmax; 149 | } 150 | 100% { 151 | mask-size: 2000vmax; 152 | } 153 | } 154 | 155 | 156 | /* gif-2 */ 157 | 158 | ::view-transition-group(root) { 159 | animation-timing-function: var(--expo-in); 160 | } 161 | ::view-transition-new(root) { 162 | mask: url('https://media.tenor.com/Jz0aSpk9VIQAAAAi/i-love-you-love.gif') center / 0 no-repeat; 163 | animation: scale 2s; 164 | } 165 | ::view-transition-old(root), 166 | .dark::view-transition-old(root) { 167 | animation: scale 2s; 168 | } 169 | @keyframes scale { 170 | 0% { 171 | mask-size: 0; 172 | } 173 | 10% { 174 | mask-size: 50vmax; 175 | } 176 | 90% { 177 | mask-size: 50vmax; 178 | } 179 | 100% { 180 | mask-size: 2000vmax; 181 | } 182 | } 183 | 184 | /* animation timing functions */ 185 | :root { 186 | --expo-in: linear( 187 | 0 0%, 0.0085 31.26%, 0.0167 40.94%, 188 | 0.0289 48.86%, 0.0471 55.92%, 189 | 0.0717 61.99%, 0.1038 67.32%, 190 | 0.1443 72.07%, 0.1989 76.7%, 191 | 0.2659 80.89%, 0.3465 84.71%, 192 | 0.4419 88.22%, 0.554 91.48%, 193 | 0.6835 94.51%, 0.8316 97.34%, 1 100% 194 | ); 195 | --expo-out: linear( 196 | 0 0%, 0.1684 2.66%, 0.3165 5.49%, 197 | 0.446 8.52%, 0.5581 11.78%, 198 | 0.6535 15.29%, 0.7341 19.11%, 199 | 0.8011 23.3%, 0.8557 27.93%, 200 | 0.8962 32.68%, 0.9283 38.01%, 201 | 0.9529 44.08%, 0.9711 51.14%, 202 | 0.9833 59.06%, 0.9915 68.74%, 1 100% 203 | ); 204 | } 205 | -------------------------------------------------------------------------------- /assets/custom-svg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rudrodip/theme-toggle-effect/df45f3602d53572b2418735bf148c4c7821e2e43/assets/og.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 10 | 14 |javascript
you need
93 |
95 | if (!document.startViewTransition) switchTheme()
96 | document.startViewTransition(switchTheme);
97 |
98 | following is a simple example, that uses a circular mask
120 | 128 |
130 |
151 |
152 | lets add a little blur to the svg
161 | 169 |
171 |
195 |
196 | let's try to pivot the center of the circle to top left
205 | 213 |
215 |
239 |
240 | 252 | we've seen all the svg mask animations, but we can use clip-paths too 253 |
254 | 262 |
264 |
300 |
301 | 302 | the issue with using clip path is that you can't do much with it, like 303 | adding gradient or blur. so svg should be a good choice for most cases 304 |
305 |307 | lets see how can we improve the clip-path animation with a custom svg with 308 | linear gradient 309 |
310 |we can use local assets too
319 | 327 |
329 |
352 |
353 | you can use gifs too
363 | 371 |
373 |
402 |
403 | this one's good 😉
412 | 420 |
422 |
451 |
452 | 453 | here are the two animation timing functions i'm using for the examples 454 |
455 |
456 |
477 |
478 | 506 |516 | 521 |507 | btw we've updated our landing 508 | pic.twitter.com/rNNavmEu5Q 509 |
510 | — Drizzle ORM (@DrizzleORM) 511 | August 5, 2024 515 |
524 |535 | 540 |525 | Shigure Ui ftw 526 | https://t.co/wK9O86PVOy 527 | pic.twitter.com/FAVVKQxdmw 528 |
529 | — SaltyAom (@saltyAom) 530 | June 24, 2024 534 |
543 |561 | 566 |544 | OK, you can do some fun things quite simply now with View 545 | Transitions API.
555 | — Lucie (@li_hbr) 556 | June 24, 2024 560 |
Thanks for showing me 546 | @rds_agi 549 | via 550 | @saltyAom 553 | ❤️ pic.twitter.com/dEBYHsmKra 554 |
569 |589 | 594 |570 | 🔦 flowplane now has a light mode
583 | — shiv (@sxhivs) 584 | June 22, 2024 588 |
credits to 571 | @rds_agi 574 | for the css transition.@_nightsweekends 578 | @_buildspace 581 | pic.twitter.com/lUFgzJrEGD 582 |
597 |610 | 615 |598 | leapflow now also has a light mode :)
604 | — Suhas Sumukh (@suhasasumukh) 605 | June 22, 2024 609 |
cc: 599 | @rds_agi 602 | pic.twitter.com/lwZr5QkcIj 603 |
618 |631 | 636 |619 | Ultimate Rick Roll with Framer 😏
625 | — Ruddro (@ruddro29) 626 | June 27, 2024 630 |
page transition effect 620 | in .@framer 623 | pic.twitter.com/qAglIZ3uMg 624 |
639 |652 | 657 |640 | 给博客加上了主题切换效果, thank you 641 | @rds_agi 644 | pic.twitter.com/0lYlc0YQZ0 645 |
646 | — Godruoyi (@godruoyi) 647 | June 25, 2024 651 |