CSS Transitions vs Animations
Understanding the architectural differences between CSS transitions and keyframe animations is critical for building performant, accessible interfaces. While both leverage the browser’s rendering pipeline, they operate on fundamentally different execution models. Transitions are state-driven and triggered by property changes, whereas animations are timeline-driven and explicitly defined. Mastering this distinction forms the foundation of Core CSS Animation Fundamentals and directly impacts frame budget allocation, compositor thread utilization, and main thread responsiveness.
Execution Models: State-Driven vs Timeline-Driven
Transitions interpolate between two computed values when a CSS property changes, requiring an initial state, a final state, and a duration. They are inherently reactive and cannot loop or define intermediate steps without JavaScript intervention. Conversely, CSS animations utilize @keyframes to define explicit intermediate states, allowing for complex sequencing, iteration counts, and fill modes. This structural difference dictates how the browser schedules work: transitions queue on the main thread until a style recalculation occurs, while animations are parsed and compiled into the compositor thread immediately upon attachment. For deeper implementation patterns on defining explicit state boundaries, refer to Keyframe Architecture & State Mapping.
Rendering Impact: composite (when restricted to transform/opacity)
Rendering Pipeline & Frame Budget Allocation
Both mechanisms can trigger style, layout, paint, or composite phases depending on the animated properties. Animating transform and opacity bypasses layout and paint, promoting elements to their own compositor layers. However, transitions on layout-triggering properties force synchronous recalculations on the main thread, causing jank. Animations suffer similarly if they manipulate non-composited properties, but they offer will-change hints to pre-allocate layers before execution begins. The interpolation speed is governed by easing functions, which must be carefully selected to match physical expectations without overloading the compositor. Detailed analysis of cubic-bezier optimization is covered in Timing Functions & Easing Curves.
Rendering Impact: composite (hardware-accelerated path)
Debugging Workflow & Performance Auditing
Effective debugging requires isolating the rendering phase responsible for dropped frames. Use the Chrome DevTools Performance panel to record a timeline and identify long tasks in the main thread or excessive layer promotions. Inspect the Layers panel to verify transform and opacity are hardware-accelerated. Avoid mixing transition and animation declarations on the same property, as the animation will override the transition’s computed value. For systematic decision trees on selecting the appropriate mechanism based on interaction complexity, consult When to use transition vs keyframe animation.
Rendering Impact: main_thread (when debugging layout/paint bottlenecks)
Code Examples
/* Transition: State-driven, single interpolation */
.card {
/* Composited properties only: avoids layout thrashing & paint */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
}
.card:hover {
transform: translateY(-4px) scale(1.02);
opacity: 0.9;
}
/* Accessibility & performance safeguard */
@media (prefers-reduced-motion: reduce) {
.card { transition: none; }
}
Optimized transition using only composited properties to avoid layout thrashing.
/* Animation: Timeline-driven, explicit sequencing */
@keyframes pulse-glow {
0%, 100% { transform: scale(1); box-shadow: 0 0 0 rgba(0,0,0,0); }
50% { transform: scale(1.05); box-shadow: 0 0 12px rgba(0,120,255,0.4); }
}
.notification-badge {
animation: pulse-glow 2s infinite ease-in-out;
/* Pre-allocates compositor layer; remove post-animation to free GPU memory */
will-change: transform, box-shadow;
}
/* Accessibility & performance safeguard */
@media (prefers-reduced-motion: reduce) {
.notification-badge { animation: none; }
}
Keyframe animation with explicit intermediate states and compositor hints.
Common Pitfalls
- Animating layout-triggering properties (
width,height,top,left) instead oftransform/translate, causing forced synchronous reflows. - Applying both
transitionandanimationto the same property, resulting in unpredictable cascade resolution and lost state. - Neglecting
prefers-reduced-motionmedia queries, which can trigger vestibular disorders or motion sickness. - Overusing
will-changewithout removing it post-animation, leading to excessive GPU memory consumption and layer explosion. - Relying on JavaScript to trigger CSS transitions without batching DOM reads/writes, causing layout thrashing.
FAQ
Can I combine CSS transitions and animations on the same element? Yes, but they must target different properties. If both target the same property, the animation rule takes precedence in the cascade, overriding the transition. Use transitions for micro-interactions (hover, focus) and animations for continuous or multi-step sequences.
Why do my CSS animations feel janky on mobile devices?
Mobile GPUs have limited VRAM. Animating non-composited properties forces the browser to recalculate layout and repaint on the main thread. Restrict animations to transform and opacity, apply will-change sparingly, and ensure the device isn’t throttling due to thermal constraints.
How do I pause a CSS animation programmatically?
Use the animation-play-state: paused property via inline styles or a CSS class toggle. This preserves the current frame position without resetting the animation timeline, unlike removing the animation name entirely.