Layout Shift caused by CSS transitions
Learn how to find and remove CSS transitions that create layout shifts
Layout Shift Caused by CSS Transitions: Understanding and Mitigating the Impact
Cumulative Layout Shifts that are caused by CSS transition often occur early during the loading phase of the page. These layout shifts do not happen consistently which makes them hard to debug.
Table of Contents!
Understanding CSS Transitions:
CSS transitions are a powerful tool for animating the change of a property over time. They are commonly used for effects such as fading, sliding, and scaling elements on a webpage. Developers can define transition effects by specifying the property to be transitioned, the duration of the transition, and the timing function governing the transition's acceleration.
Transsition can have a property, duration, timing-function and a delay. A transition shorthand looks like this:
/* property | duration | timing-function | delay */ transition: margin-right 4s ease-in-out 1s;
Layout Shifts: The Unintended Consequence:
Layout shifts occur when elements on a webpage change position or size, causing other elements to reflow and the overall layout of the page to shift. While CSS transitions are designed to provide smooth animations, they can inadvertently trigger layout shifts, leading to a jarring and disruptive user experience. The most common causes of layout shifts during CSS transitions include changes in dimensions, position, or visibility of elements.
Cumulatieve Layout Shifts caused by CSS transitions usually occur when a above-the-fold element like a navigation menu transitions from their first (unstyled) state to their final (styled or even hidden) state. This is usually an unintended consequence of overly broad transition property's. For example a menu entry should only transition background color and instead of the transition property 'background-color' 'all' has been chosen. This will lead to not only a background transition but in some cases also a width, height or even visibility transition during page load.
Take a look at the example below. This demonstrates a layout shift caused by CSS transitions that occur during the loading phase of a page. Unfortunately I see this pattern all the time and finding and fixing these kinds of issues can be difficult.
Find and fix CSS transitions:
To find and fix all layout shifts caused by CSS transitions we need do a quick test. First we need to find all CSS transitions. When we have done this we need to ensure the transition does not change the position (width, height,margin,padding, visibility) of an element. We can do this by modifying or disabling these transitions. Then finally we can test the impact of these changes and decide one and for all if CSS transitions are causing CLS issues.
Core Web Vitals tip: Cumulative Layout Shifts that are caused by CSS transition often occur early during the loading phase of the page. These layout shifts do not happen consistently which makes them hard to debug. Slowing down your network by emulating a mobile device and disabling your cache will make finding them easier!
Step 1: Find CSS transitions
Finding CSS transitions can be done manually: inspect all the stylesheets and search for the word 'transition'. That should not be more then 10 minutes work but there is a better way! Just paste this snippet in the console and press enter
(() => { let nodeTable = []; let nodeArray = []; // Get the name of the node function getName(node) { const name = node.nodeName; return node.nodeType === 1 ? name.toLowerCase() : name.toUpperCase().replace(/^#/, ''); } // Get the selector const getSelector = (node) => { let sel = ''; try { while (node && node.nodeType !== 9) { const el = node; const part = el.id ? '#' + el.id : getName(el) + (el.classList && el.classList.value && el.classList.value.trim() && el.classList.value.trim().length ? '.' + el.classList.value.trim().replace(/\s+/g, '.') : ''); if (sel.length + part.length > (100) - 1) return sel || part; sel = sel ? part + '>' + sel : part; if (el.id) break; node = el.parentNode; } } catch (err) { // Do nothing... } return sel; }; const getNodesWithTransition = (node) => { // Get the computed style let cs = window.getComputedStyle(node); let tp = cs['transition-property']; let td = cs['transition-duration']; // If there is a transition, add it to the table if (tp !== '' && tp !== 'none' && td != '0s') { nodeTable.push({ selector: getSelector(node), transition: cs['transition'] }); nodeArray.push(node); } // Recursively call this function for each child node for (let i = 0; i < node.children.length; i++) { getNodesWithTransition(node.children[i]); } } // find all transitions getNodesWithTransition(document.body); // Display the results in the console console.log('%cReadable table of selectors and their transitions', 'color: red; font-weight: bold;'); console.table(nodeTable); console.log('%cNodeList for you to inspect (harder to read but more info)', 'color: red; font-weight: bold;'); console.log(nodeArray); // styles to temporarity override the transitions let selectors = nodeTable.map((item) => item.selector).join(', '); console.log('%cSpecific CSS to disable all transitions on this page', 'color: red; font-weight: bold;'); console.log(`<style>${selectors}{transition-property: none !important;}</style>`); console.log('%cGlobal CSS to disable all transitions on this page (not suggested on production)', 'color: red; font-weight: bold;'); console.log(`<style>*{transition-property: none !important;}</style>`); })()
It will show you a table off all the transitions, the elements they are working on and more detail about the transitions.
To find layout shift we need to be look for transition property's like width,height, margin,padding, transform, display and especially all (since all includes all valid transition properties)
Step 2: Modify CSS transitions
The above JavaScript snippet will show all the transitions as well as provide example code on how to disable those transitions. For quick testing purposes I suggest that to take the easy road and disable all transitions with one simple line of CSS code
<style>*{transition-property: none !important;}</style>
Of course for live environments a bit more finesse is required. Carefully only remove unneeded transition-properties on a per-selector-base. For example change #button{transition: all .2s}
to #button{transition: background-color .2s}
Step 3: Measure the change in layout shift
Other transition good-practices:
- Prefer GPU Acceleration: Utilizing GPU acceleration for CSS transitions can offload the rendering workload from the CPU to the GPU. This can be achieved by ensuring that the properties being transitioned are conducive to GPU acceleration, such as opacity and transform.
- Use the "will-change" Property: The
will-change
CSS property informs the browser that a specific element is likely to be changed, allowing it to optimize rendering accordingly. - Ensure Consistent Dimensions: To prevent layout shifts caused by changes in dimensions, ensure that elements have consistent dimensions before and after the transition. This may involve setting explicit dimensions, using percentage-based values, or employing techniques like aspect ratio boxes.
- Optimize Timing Functions: The choice of timing function can significantly impact the perception of smoothness during a transition. Be mindful of the acceleration and deceleration patterns, and consider using
ease-in-out
or custom cubic bezier functions for a more natural feel.
Stop Guessing, Start Measuring
I use Core/Dash to optimize my clients sites. So Should You.
- Easy set up.
- Over 5000 happy clients
- Free trial, No strings attached