2024-09-29-using-steps-for-performant-css-transitions.md
1 +++ 2 title = "Using steps() for performant CSS transitions" 3 date = 2024-09-29 4 draft = false 5 6 [taxonomies] 7 tags = ["JavaScript", "CSS", "Web Development"] 8 +++ 9 10 I've used a lot of [CSS transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions) over the years. Shifting between values in a smooth fashion is pretty easy with a simple one-liner. 11 12 ```css 13 transition: background-color 2s; 14 ``` 15 16 But what if you have a lot of transitions? What if you have too many transitions? Your browser tries to run them as fast as your monitor's refresh rate allows (60 times per second, or more). This can be pretty taxing. 17 18 Recently I ran into this problem transitioning many elements of a large SVG with a bunch of complex shapes. With all the elements transitioning all at once, the page scrolling started to stutter quite noticeably. 19 20 Here's how I solved it. Instead of a transition, I used [CSS animations](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations), with `steps()` to effectively "bring down the frame rate" and stop the browser from doing too much work. 21 22 ```css 23 @keyframes color-change { 24 100% { 25 fill: rgb(12, 169, 210); 26 } 27 } 28 29 .do-transition { 30 animation-name: color-change; 31 animation-duration: 5s; 32 animation-iteration-count: infinite; 33 animation-timing-function: steps(10); 34 } 35 ``` 36 37 Or you can use the shorthand. 38 39 ```css 40 .do-transition { 41 animation: color-change 5s infinite steps(10); 42 } 43 ``` 44 45 Just add the `do-transition` class to the element you want to animate. Adjust the `animation-duration` and the `steps()` count to get a balance between smoothness and performance. 46 47 ```js 48 const polygon = svg.querySelector("polygon"); 49 polygon.classList.add("do-transition"); 50 ``` 51 52 Here's a little demo with a simple SVG. I've kept the `steps()` low to make the effect more noticeable. 53 54 <svg id="svg-interactive-21e12855-1b41-48d1-97c3-24a4f5f751c4" xmlns='http://www.w3.org/2000/svg' width='240' height='240' viewBox='0 0 240 240' patternUnits="userSpaceOnUse"> 55 <defs> 56 <pattern id="smallGrid" width="120" height="120" patternUnits="userSpaceOnUse"> 57 <rect fill='#ddffaa' width='120' height='120'/> 58 <polygon fill='#AE9' fill-opacity='1' points='120 120 60 120 90 90 120 60 120 0 120 0 60 60 0 0 0 60 30 90 60 120 120 120'/> 59 </pattern> 60 </defs> 61 <rect width="240" height="240" fill="url(#smallGrid)" /> 62 </svg> 63 64 <script> 65 const svg = document.getElementById('svg-interactive-21e12855-1b41-48d1-97c3-24a4f5f751c4'); 66 67 const polygon = svg.querySelector('polygon'); 68 polygon.classList.add('do-transition'); 69 70 const rect = svg.querySelector('rect'); 71 rect.classList.add('do-transition'); 72 </script> 73 74 <style> 75 @keyframes color-change { 76 100% { fill: rgb(12, 169, 210); } 77 } 78 79 .do-transition { 80 animation-name: color-change; 81 animation-duration: 5s; 82 animation-iteration-count: infinite; 83 animation-timing-function: steps(10); 84 } 85 </style> 86 87 That's the basic idea anyway. I hope it helps if you run into a similar problem. 88 89 (Bonus tip: Use `animation-delay` to stagger the animations if you have a lot of elements transitioning at once so they don't all paint on the screen at once on each `step()`) 90 91 Happy coding! 🚀