CSS transitions and transforms work together by transitioning the transform property value smoothly over time, with transforms providing GPU-accelerated animation and transitions controlling the timing.
Basic Mechanism:
- transform - Defines the visual transformation (translate, rotate, scale, skew)
- transition - Defines which properties to animate, duration, and timing function
Example Tab Indicator:
.tabs {
position: relative;
}
.tab-indicator {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: blue;
width: 100px; /* Width of first tab */
transition: transform 0.3s ease; /* Animate transform property */
will-change: transform; /* Hint browser for GPU optimization */
}
/* When second tab is active, translate indicator */
.tabs[data-active="1"] .tab-indicator {
transform: translateX(100px); /* Move 100px right */
}
.tabs[data-active="2"] .tab-indicator {
transform: translateX(200px); /* Move 200px right */
}
Why Use Transform Instead of Left/Right:
| Property |
Triggers |
GPU Accelerated |
Performance |
60 FPS |
| left/right/top/bottom |
Layout + Paint + Composite |
No |
Slow |
Difficult |
| transform: translate() |
Composite only |
Yes |
Fast |
Easy |
GPU Acceleration:
Transforms are composited on the GPU without triggering layout recalculations or repaints:
/* SLOW - Causes reflow */
.indicator {
left: 0;
transition: left 0.3s ease;
}
.indicator.moved {
left: 100px; /* Browser recalculates layout */
}
/* FAST - GPU composited */
.indicator {
transform: translateX(0);
transition: transform 0.3s ease;
}
.indicator.moved {
transform: translateX(100px); /* No layout recalculation */
}
Complete Tab Indicator Example:
<div class="tabs" id="tabs">
<button class="tab active" data-index="0">Home</button>
<button class="tab" data-index="1">About</button>
<button class="tab" data-index="2">Contact</button>
<div class="tab-indicator"></div>
</div>
.tabs {
position: relative;
display: flex;
border-bottom: 1px solid #ddd;
}
.tab {
padding: 12px 24px;
border: none;
background: transparent;
cursor: pointer;
position: relative;
}
.tab-indicator {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
width: 64px; /* Initial width */
background: #007bff;
transform: translateX(0); /* Initial position */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
will-change: transform, width;
}
const tabs = document.querySelectorAll('.tab');
const indicator = document.querySelector('.tab-indicator');
tabs.forEach(tab => {
tab.addEventListener('click', (e) => {
const index = tab.dataset.index;
const offsetLeft = tab.offsetLeft;
const width = tab.offsetWidth;
indicator.style.transform = `translateX(${offsetLeft}px)`;
indicator.style.width = `${width}px`;
});
});
Transition Timing Functions:
/* Linear - constant speed */
transition: transform 0.3s linear;
/* Ease - slow start and end (default) */
transition: transform 0.3s ease;
/* Ease-in-out - smoother than ease */
transition: transform 0.3s ease-in-out;
/* Cubic bezier - custom curve */
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* Material Design easing */
transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
Multiple Transform Properties:
.tab-indicator {
transform: translateX(100px) scaleX(1.2); /* Combine transforms */
transition: transform 0.3s ease;
}
Will-Change Optimization:
.tab-indicator {
will-change: transform; /* Hints browser to optimize */
transition: transform 0.3s ease;
}
/* Remove will-change after transition */
.tab-indicator:not(.animating) {
will-change: auto;
}
Best Practices:
- Always transition transform, not positional properties (left/top/right/bottom)
- Use will-change sparingly - only on elements that will animate
- Combine transforms in single declaration:
transform: translateX(10px) scale(1.1)
- Use cubic-bezier for natural motion:
cubic-bezier(0.4, 0, 0.2, 1)
- Keep transitions under 300ms for responsive feel
Common Pitfall:
/* WRONG - Overwrites previous transform */
.element {
transform: translateX(100px);
transform: scale(1.2); /* This overwrites translateX */
}
/* CORRECT - Combine in one declaration */
.element {
transform: translateX(100px) scale(1.2);
}
Accessibility:
/* Respect user's motion preferences */
@media (prefers-reduced-motion: reduce) {
.tab-indicator {
transition: none; /* Disable animation */
}
}