Static websites are a thing of the past. Users expect movement — subtle hover effects, loading animations, scroll-triggered transitions, and attention-grabbing hero animations. CSS animations make all of this possible without JavaScript, and they run on the browser's compositor thread for silky-smooth performance.
This guide covers everything from basic transitions to complex keyframe animations, with practical patterns and tips for using an animation generator to speed up your workflow.
Before diving into animations, it is important to understand the distinction between transitions and animations, because they serve different purposes.
Transitions animate property changes between two states (usually triggered by pseudo-classes like :hover). They are simple, declarative, and perfect for micro-interactions:
.button {
background: #8b5cf6;
transition: background 0.3s ease, transform 0.2s ease;
}
.button:hover {
background: #7c3aed;
transform: translateY(-2px);
}
Animations use @keyframes to define multiple steps and can run automatically, loop, or be controlled with classes. They are more powerful and flexible:
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero-text {
animation: fadeInUp 0.8s ease-out forwards;
}
Keyframes define what happens at each step of the animation:
@keyframes animation-name {
0% { /* starting state */ }
50% { /* midpoint */ }
100% { /* end state */ }
}
You can use percentages or the keywords from (0%) and to (100%). You can have as many keyframe steps as you need.
The animation property is a shorthand for up to eight sub-properties:
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
| Property | Description | Example |
|---|---|---|
animation-name | Name of the @keyframes | fadeInUp |
animation-duration | Length of one cycle | 0.8s |
animation-timing-function | Easing curve | ease-out, cubic-bezier(...) |
animation-delay | Delay before starting | 0.2s |
animation-iteration-count | Number of loops (or infinite) | 3, infinite |
animation-direction | normal, reverse, alternate | alternate |
animation-fill-mode | Styles before/after animation | forwards |
animation-play-state | running or paused | paused |
/* Full shorthand */
.element {
animation: fadeInUp 0.8s ease-out 0.2s 1 normal forwards;
}
The timing function determines how the animation accelerates and decelerates. The built-in options are:
For custom motion curves, use cubic-bezier():
/* Spring-like bounce */ animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* Dramatic deceleration */ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
The most common entrance animation. Elements slide up and fade in as they appear:
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
Elements grow from a small size to full size, creating a "pop" effect:
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.8); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-40px); }
to { opacity: 1; transform: translateX(0); }
}
A subtle scale pulse that draws attention without being distracting:
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes rotateIn {
from { opacity: 0; transform: rotate(-10deg) scale(0.9); }
to { opacity: 1; transform: rotate(0deg) scale(1); }
}
A moving gradient that simulates content loading:
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg, #1e293b 25%, #2d2d3f 50%, #1e293b 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
Simulate a typewriter with steps and a blinking cursor:
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
@keyframes blink {
50% { border-color: transparent; }
}
.typewriter {
overflow: hidden;
white-space: nowrap;
border-right: 2px solid #8b5cf6;
animation: typing 3s steps(30) forwards, blink 0.7s step-end infinite;
}
Modern CSS offers ways to trigger animations when elements enter the viewport without JavaScript:
@keyframes reveal {
from { opacity: 0; transform: translateY(40px); }
to { opacity: 1; transform: translateY(0); }
}
.scroll-reveal {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
For broader browser support, use a small JavaScript snippet with IntersectionObserver to add a class when elements become visible:
/* CSS */
.reveal { opacity: 0; transform: translateY(30px); transition: all 0.8s ease-out; }
.reveal.visible { opacity: 1; transform: translateY(0); }
/* JS */
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); } });
});
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
Not all CSS properties are equal when it comes to animation performance. The key distinction is between properties that trigger layout (reflow) and those that only trigger composite (paint + composite).
transform (translate, scale, rotate)opacityfilter (blur, brightness, etc.)These run on the GPU compositor thread and can hit 60fps easily.
width, height, margin, paddingtop, left, right, bottomfont-size, line-heightThese force the browser to recalculate layout, repaint, and composite on every frame — expensive and janky.
The golden rule: if you can achieve the same visual result withtransformandopacity, always do so. These are the only two properties that can be animated without triggering layout or paint.
Always respect the prefers-reduced-motion media query. Some users experience motion sickness or have vestibular disorders:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
This globally disables animations for users who prefer reduced motion, replacing them with instant state changes.
Writing keyframe animations from scratch requires trial and error — tweaking values, reloading, and repeating. A visual animation generator lets you preview animations in real time, adjust timing curves with interactive editors, and export production-ready CSS.
RiseTop's CSS Animation Generator provides a library of preset animations (fade, slide, scale, rotate, pulse, bounce), customizable timing functions, delay and duration controls, and one-click CSS export. It is the fastest way to add polished animations to your project.
transform and opacity for smooth 60fps animations.animation-fill-mode: forwards, elements snap back to their pre-animation state after the animation ends.will-change: transform, opacity hints the browser to optimize, but use it sparingly and remove it after the animation.CSS animations are a powerful tool for creating engaging, performant web experiences. By understanding the difference between transitions and keyframe animations, choosing the right timing functions, and always animating transform and opacity for performance, you can add motion to your websites that feels natural and polished. Use a visual generator to experiment faster, and always respect your users' motion preferences.
Try our free CSS Animation Generator — design, preview, and copy production-ready CSS instantly.
Open CSS Animation Generator →