Animating expandable UI elements like menus, accordions, and collapsible sections is a common pattern, but doing it wrong can destroy your site's performance. This guide reveals the professional technique that keeps animations silky smooth at 60fps.
Modern web applications frequently need expand and collapse functionality, yet many developers unknowingly use approaches that trigger expensive layout recalculations. By combining scale transforms with dynamically generated CSS keyframes, we can achieve buttery-smooth animations that run entirely on the GPU without blocking the main thread.
These animation techniques are essential for creating polished web applications that delight users while maintaining optimal performance across all devices.
The Problem with Common Approaches
Animating Width and Height Directly
The most straightforward approach to creating expandable animations involves directly animating the width and height CSS properties. While this might seem intuitive, it comes with significant performance drawbacks that can make your animations feel sluggish and unresponsive.
When you animate width and height, the browser is forced to recalculate the layout for every single frame of the animation. This process, known as layout recalculation or reflow, is computationally expensive because the browser must determine how all elements are positioned and sized within the document. Every time the dimensions change, the browser must:
- Recalculate element positions across the entire document
- Determine how sibling and parent elements are affected by the size change
- Update the geometric information for all impacted elements in the render tree
- Repaint the changed regions on the screen
This means that even a simple expand animation can trigger hundreds of layout operations per second. The animation may skip frames or feel unresponsive, particularly on mobile devices with less processing power. According to the Chrome Developers animation guide, animating layout properties like width and height forces synchronous layout recalculation, which can cause significant frame drops during animations.
Using CSS clip or clip-path Properties
Another approach that developers sometimes use is animating the CSS clip property or its modern replacement, clip-path. While these properties can create clipping effects that make elements appear to expand or collapse, they come with their own set of limitations.
The clip property has been deprecated and should not be used in new projects. Its modern replacement, clip-path, offers more flexibility but still requires the element to be absolutely or fixed positioned in many cases. Additionally, clip-path animations still trigger paint operations, which while less expensive than layout recalculations, still require the browser to redraw pixels on the screen.
The fundamental issue with clip-based approaches is that they don't actually change the element's dimensions--they simply hide portions of the element through clipping. This means that while the visual effect might look similar to an expandable animation, the underlying element still occupies its full expanded space in the document flow, which can cause unexpected layout shifts and accessibility issues for users relying on assistive technologies.
The Scale Transform Solution
Why Transforms Are Performant
CSS transforms represent a fundamental shift in how we approach web animations. Unlike width, height, or other layout-affecting properties, transforms operate at a completely different level of the browser's rendering pipeline. As explained in web.dev's guide to high-performance animations, the transform and opacity properties are the only ones that can be animated efficiently because they don't affect document layout.
The key insight is that transforms don't change the document flow or affect the positioning of other elements. Instead, they apply visual transformations to elements that have already been laid out. This means the browser can treat transforms as purely visual operations that don't require recalculating the page layout.
The transform property is what's known as a compositor-only property, meaning it can be handled entirely by the browser's compositor thread without involving the main JavaScript execution thread. This separation is crucial for maintaining smooth animations even when the main thread is busy with other tasks, as the GPU handles all frame-by-frame updates independently.
How Scale Transforms Create the Expand Effect
The core idea behind performant expandable animations is deceptively simple: instead of actually changing an element's width and height, we use the scale transform to visually shrink or grow the element. When an element is collapsed, we scale it down; when it's expanded, we scale it back up to its natural size.
This approach works because the scale transform affects only the visual representation of the element, not its actual dimensions in the document. The element maintains its full expanded size in terms of layout, but visually appears smaller when scaled down. This means no layout recalculations occur during the animation--only compositing operations on the GPU.
However, there's a catch: when you scale down the parent container, all its child elements are scaled down proportionally. The solution to this problem is elegant: apply an inverse scale transform to the child elements that counteracts the scale of the parent. If the parent is scaled to 0.5, the children are scaled to 2.0, effectively preserving their visual appearance while the parent container animates smoothly.
Understanding these transform techniques pairs well with learning about CSS custom properties for creating maintainable, responsive designs that incorporate smooth animations.
1function calculateCollapsedScale() {2 // The menu title can act as the marker for the collapsed state3 const collapsed = menuTitle.getBoundingClientRect();4 5 // Whereas the menu as a whole (title plus items) can act as6 // a proxy for the expanded state7 const expanded = menu.getBoundingClientRect();8 9 return {10 x: collapsed.width / expanded.width,11 y: collapsed.height / expanded.height12 };13}Step 2: Building CSS Animations on the Fly
The Dynamic Keyframes Technique
Generating CSS keyframes dynamically using JavaScript is the key to achieving both performance and flexibility in expandable animations. This approach might seem unusual at first--after all, CSS is typically static--but it's the key to creating animations tailored to specific content dimensions.
The technique works by creating a @keyframes animation definition as a string in JavaScript, then injecting it into the page as a style element. As documented in the Chrome Developers guide on performant expand and collapse animations, this approach allows you to create animations that match your particular use case with precise control over every aspect of the motion.
The main benefit is that the resulting animations run on the compositor thread, completely independent of the main JavaScript thread. Once the keyframes are injected and the animation is triggered, the browser handles all frame-by-frame updates without any JavaScript execution. This means animations won't be affected by JavaScript busy states, event handlers, or other main thread activity.
Implementing the Easing Function
For truly smooth expandable animations, you need sophisticated easing that matches the physics of real-world motion. The solution is to pre-calculate the easing at each step of your keyframe animation, rather than relying on the browser's built-in easing between keyframes.
The easing calculation loop works by iterating through 100 steps (0% to 100%) and at each step, applying your easing function to remap the linear progress to a curved one. For example, an ease-out curve starts fast and slows down at the end, which feels more natural for expandable animations that should reveal content quickly but settle gently.
For each eased step, you calculate both the forward scale (for the parent container) and the inverse scale (for the child content). The inverse scale counteracts the parent's transformation, keeping child elements at their natural visual size throughout the animation.
1function createKeyframeAnimation() {2 let { x, y } = calculateCollapsedScale();3 let animation = '';4 let inverseAnimation = '';5 6 for (let step = 0; step <= 100; step++) {7 // Remap the step value to an eased one8 let easedStep = ease(step / 100);9 10 // Calculate the scale of the element11 const xScale = x + (1 - x) * easedStep;12 const yScale = y + (1 - y) * easedStep;13 14 animation += `${step}% {15 transform: scale(${xScale}, ${yScale});16 }`;17 18 // Calculate the inverse scale for the content19 const inverseX = 1 / xScale;20 const inverseY = 1 / yScale;21 22 inverseAnimation += `${step}% {23 transform: scale(${inverseX}, ${inverseY});24 }`;25 }26 27 return { animation, inverseAnimation };28}Step 3: Enabling the CSS Animations
Injecting Styles and Triggering Animation
With dynamic keyframes generated, the final step is to inject them into the page and apply the animation to your elements. This involves creating a style element, populating it with your generated @keyframes rules, and then adding the animation to the elements you want to animate.
The injection process creates a new <style> element and sets its textContent to include the @keyframes definitions. Each animation gets a unique name (like expand or collapse) that corresponds to the direction of the motion. The style element is then appended to the document head, making the animations available throughout the page.
When triggering the animation, you use the animation-name property to reference your dynamically created keyframes, combined with animation-duration to control how long the animation takes. Setting animation-fill-mode to "forwards" ensures the element maintains its final animated state after the animation completes. For best results, set animation-timing-function to match your easing curve.
Managing Animation State and Transitions
In real-world applications, users may click to expand an element multiple times, and the animation needs to handle transitions in both directions smoothly. This requires careful state management to ensure the animation can reverse direction without glitching or jumping.
A robust approach is to always regenerate the keyframes just before applying an animation, ensuring they reflect the current state of the element. This adds a small amount of overhead but guarantees smooth animation regardless of how many times it's been triggered. When the user clicks to expand, you generate keyframes for the expand direction; when they click to collapse, you generate keyframes for the collapse direction.
Using CSS class toggling combined with animation event listeners provides another pattern. When the user clicks, you toggle a class and the CSS handles the visual change. However, with dynamically generated keyframes, the keyframes themselves need to be regenerated based on current element dimensions before each animation begins.
These state management patterns are particularly important when building interactive web applications that handle complex user interactions with multiple animated components.
Performance Optimization
Using will-change and Compositor Hints
To get the best possible performance from your expandable animations, provide hints to the browser about upcoming changes using the CSS will-change property. As recommended in web.dev's animation performance guide, this tells the browser to optimize for upcoming changes to a specific property, allowing it to make preparations in advance.
For scale transform animations, add will-change: transform to animated elements. This prompts the browser to create a separate compositing layer, so the GPU handles the animation without waiting for the CPU to repaint on each frame. It's also important to set transform-origin to define the pivot point for scaling--typically top left for expandable menus and accordions.
.menu {
will-change: transform;
transform-origin: top left;
}
.menu-content {
will-change: transform;
}
Use will-change judiciously, as creating too many compositing layers can hurt performance by consuming excessive GPU memory. Only add it to elements actually being animated, and consider removing it after the animation completes if the element won't be animated again soon.
Avoiding Layout Thrashing
A common pitfall is layout thrashing, which occurs when JavaScript reads layout properties (like getBoundingClientRect()) and then modifies styles, forcing multiple layout recalculations. This causes significant performance degradation, especially during animations.
The solution is to batch all layout reads together before making any writes. If you need to measure elements and then apply styles, do all measurements first, then make all changes afterward. This allows the browser to perform a single layout recalculation instead of multiple ones. If element dimensions don't change between animations (in a component where content is static but visibility changes), cache the measurements and reuse them for subsequent animations.
GPU-Accelerated
Transforms run on the compositor thread, freeing up the main JavaScript thread for other tasks and ensuring smooth 60fps animations
No Layout Recalculations
Scale transforms don't trigger expensive reflow operations like width/height animations, avoiding frame drops
60fps Smoothness
Properly optimized animations maintain full frame rate even on mobile devices with limited processing power
Bidirectional Support
Dynamic keyframes work seamlessly for both expand and collapse transitions with consistent performance
Common Use Cases
Expandable Navigation Menus
Navigation menus are one of the most common use cases for expandable animations. Whether it's a mobile hamburger menu, a dropdown navigation bar, or an accordion-style menu, the techniques in this guide apply directly. For menus, apply the inverse scale technique to each menu item or to a wrapper around the menu content so items maintain their visual proportions during the animation.
Accordion Sections
Accordions present a different challenge because multiple sections may need to animate simultaneously. When one section expands, others might collapse, and the overall page layout needs to accommodate these changes smoothly. Apply the scale transform technique to each accordion section independently, allowing each to animate without affecting the others during the animation.
Collapsible Detail Sections
Detail sections that expand inline, like "read more" links or expandable form fields, follow the same fundamental pattern. The scale transform technique works well here because it maintains the element's layout space while animating its visual size, preventing surrounding content from jumping around during the animation.
Frequently Asked Questions
Conclusion
Creating performant expandable animations requires understanding how the browser's rendering pipeline works and leveraging GPU-accelerated transforms instead of layout-affecting properties. By following these techniques, you'll create expandable UI elements that feel professional, polished, and performant.
Key Takeaways:
- Always use CSS transforms for expandable animations instead of animating width or height to avoid expensive layout recalculations
- Apply inverse scaling to child elements to maintain their visual appearance while the parent container animates
- Generate keyframes dynamically to create animations tailored to your specific content dimensions
- Use browser hints like will-change to optimize animation performance, but use them judiciously
- Respect user preferences for reduced motion and maintain accessibility with proper ARIA attributes throughout the animation
For more on creating performant web experiences, explore our web development services or learn about responsive design techniques that complement smooth animations.
Ready to Build High-Performance Web Animations?
Our team of expert developers can help you implement smooth, performant animations that enhance user experience without compromising performance. From expandable menus to complex interactive components, we build animations that feel professional and polished.
Sources
- Chrome Developers: Building performant expand & collapse animations - Comprehensive guide covering the scale transform technique, CSS clip animation limitations, and dynamic keyframe generation
- web.dev: How to create high-performance CSS animations - Core reference for transform and opacity properties as the only performant animation targets
- MDN: CSS will-change property - Documentation on compositor hints for animation optimization