tool / css animation@keyframes · on scroll
CSS keyframes animation generator
Pick an entrance, an attention-seeker or an exit, set its duration, delay, repeats, direction, fill and easing, and watch it run on a card. Copy the @keyframes and the class, the one-line shorthand, or a version that plays as the element scrolls into view.
- Effects
- 27
- Timing functions
- 9
- Code outputs
- 4
- scrollReveal, gzipped
- 5.8 KB
Make a CSS animation
Entrances · 13
Attention · 7
Exits · 7
Fade up. Fades in while rising 32px into place. Picking an effect sets the timing it is meant for; change any of it below, or pick it again to go back.
On load or when a class is added: CSS, or the same as a shorthand. When it scrolls into view: CSS on a scroll timeline, or svg-scroll-draw in every browser.
/* Fade up. Fades in while rising 32px into place.
Markup: <div class="fade-up">…</div> */
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(32px);
}
to {
opacity: 1;
transform: translateY(0px);
}
}
.fade-up {
animation: fade-up 600ms ease-out both;
}
/* Asked for less motion: the same animation as one instant frame, so the
element ends where it would have and animationend still fires. */
@media (prefers-reduced-motion: reduce) {
.fade-up {
animation-duration: 0.01ms;
}
}When it scrolls into view · scroll the box, not the page
view() · entry 0% cover 30%
top 88% → top 53%
- CSS The On scroll · CSS tab, scoped to this box, which is the card’s nearest scroller and so its view() timeline.
- scrollReveal scrollReveal has no scrollContainer option, so the box runs the same from and to through scrollAnimate, the engine scrollReveal is built on, with the box as its scrollContainer.
Delay, duration, cycles, fill
An animation is a stretch of time. It waits for its animation-delay, then runs animation-iteration-count cycles of animation-duration each, every cycle carrying the element through the keyframes from 0% to 100%, or back, as animation-direction says. Outside those cycles the keyframes do not apply, unless animation-fill-mode holds the first frame through the delay (backwards) or the last one after the end (forwards), as fig. 1 draws.
/* The generator's default: fade up. */
.fade-up {
animation: fade-up 600ms ease-out both;
}animation-name
none by default. Which @keyframes to run. Nothing runs without it.
animation-duration
0s by default. How long one cycle takes. The default is zero (newer specs spell it auto, which is zero for an animation that runs on time), so a shorthand without a time does nothing you can see.
animation-timing-function
ease by default. The easing, applied to each stretch between two keyframes, not once across the whole cycle.
animation-delay
0s by default. How long to wait before the first cycle. A negative delay starts part of the way through.
animation-iteration-count
1 by default. How many cycles: a number, fractions included, or infinite.
animation-direction
normal by default. Which way each cycle runs: normal, reverse, or alternate and alternate-reverse, which turn round every cycle.
animation-fill-mode
none by default. Whether the keyframes apply outside the cycles: backwards during the delay, forwards after the end, both for both.
animation-play-state
running by default. paused holds the animation where it is; running carries on from there.
ease-out on a five-stop bounce eases each of its four stretches, not the whole. A keyframe can set its own animation-timing-function for the stretch that starts there.@keyframes: from, to and the stops between
A keyframes rule lists offsets and what the element looks like at each. from is 0% and to is 100%; any percentage between adds a stop, and several offsets can share one block. A property a keyframe leaves out is carried between the keyframes that do set it, and a missing 0% or 100% falls back to the element’s own style. Here the heartbeat’s rests share one line:
@keyframes heartbeat {
0%, 24%, 56%, 100% {
transform: scale(1);
}
12% {
transform: scale(1.12);
}
36% {
transform: scale(1.1);
}
}Every effect in the generator animates only transform, opacity and filter, which browsers can usually animate without laying the page out again on every frame. Animating width, top or margin works too, but moves everything around the element each frame. A pivot such as swing’s top edge is set with transform-origin on the element, outside the keyframes.
| Effect | What it does | Stops | On scroll in JS |
|---|---|---|---|
| Entrances · From somewhere else to where the element is. | |||
| fade-in | Opacity from 0 to 1, nothing moves. | 2 | scrollReveal |
| fade-up | Fades in while rising 32px into place. | 2 | scrollReveal |
| fade-down | Fades in while dropping 32px into place. | 2 | scrollReveal |
| fade-left | Fades in while moving 32px to the left, into place. | 2 | scrollReveal |
| fade-right | Fades in while moving 32px to the right, into place. | 2 | scrollReveal |
| zoom-in | Grows from 60% of its size as it fades in. | 2 | scrollReveal |
| flip-in-x | Turns down into place about its horizontal axis, in perspective. | 2 | scrollAnimate |
| flip-in-y | Turns into place about its vertical axis, in perspective. | 2 | scrollAnimate |
| slide-in | Slides in from the left by its own width, fully opaque. | 2 | scrollAnimate |
| blur-in | Comes into focus from a 12px blur as it fades in. | 2 | scrollAnimate |
| rotate-in | Turns 45° and grows from 80% as it fades in. | 2 | scrollReveal |
| bounce-in | Pops up past full size, settles back, then lands. | 5 | CSS only |
| drop-in | Falls from 64px above, overshoots a little, and settles. | 4 | CSS only |
| Attention · Start and end where the element is; usually repeated. | |||
| pulse | Swells to 106% and back, on a loop. | 3 | CSS only |
| shake | Side to side, each swing smaller than the last. | 8 | CSS only |
| wobble | Sways and tilts side to side, dying away. | 7 | CSS only |
| heartbeat | Two quick beats, then a rest, on a loop. | 6 | CSS only |
| jello | Skews one way and the other, settling like a jelly. | 8 | CSS only |
| swing | Hangs from its top edge and swings to rest. | 6 | CSS only |
| float | Drifts 10px up and back down, slowly, on a loop. | 3 | CSS only |
| Exits · From where the element is to gone. | |||
| fade-out | Opacity from 1 to 0, nothing moves. | 2 | scrollAnimate |
| fade-out-up | Fades out while rising 32px. | 2 | scrollAnimate |
| fade-out-down | Fades out while dropping 32px. | 2 | scrollAnimate |
| zoom-out | Shrinks to 60% of its size as it fades out. | 2 | scrollAnimate |
| flip-out-x | Turns away about its horizontal axis, in perspective. | 2 | scrollAnimate |
| slide-out | Slides out to the right by its own width, fully opaque. | 2 | scrollAnimate |
| blur-out | Goes out of focus to a 12px blur as it fades out. | 2 | scrollAnimate |
Run it when it scrolls into view
The same keyframes can follow the scroll instead of a clock. In CSS, animation-timeline: view() ties the animation to where the element is in its scroller and animation-range picks the stretch: the generator plays entrances and attention effects over entry 0% cover 30%, from the moment the element’s top comes in at the bottom to 30% of the way across, and exits over exit 0% exit 100%, as it leaves at the top. The scroll sets the pace, so the duration, delay and count drop out, and scrolling back plays it backwards. It runs in Chrome and Edge 115, Safari 26 and Firefox 159 or later, so it sits inside @supports, where older browsers simply show the element.
svg-scroll-draw does the same in every browser, from one value to another. scrollReveal plays 7 of the 27 effects with its own presets and from (opacity, x, y, scale and rotations, in px and degrees), from when the element’s top passes 88% of the window to 53%, and keeps it once played. scrollAnimate takes the CSS values themselves, so it plays 11 more: blurs, perspective flips, slides by a percentage, and the exits. The other 9 have stops between their two ends, which a from and a to cannot hold; their scroll version is CSS only.
once: true, on by default in scrollReveal, keeps an entrance in place after it has played. With a cubic-bezier that goes past 1, svg-scroll-draw 2.23.2 and earlier keep the highest eased value reached, so the element stops on the overshoot; 2.23.3 keeps the furthest scroll progress and eases it after, so it settles where it should. The generated code says so when your curve overshoots.More on the CSS side: the CSS scroll-driven animation generator. More on the library side: the scroll reveal generator.
Reduced motion, built in
Every CSS output carries a prefers-reduced-motion rule. It does not remove the animation: it makes it one instant frame, so the element ends where it would have, an exit still hides it, and any script waiting for animationend still hears it. An infinite loop runs once.
/* Asked for less motion: the same animation as one instant frame, so the
element ends where it would have and animationend still fires.
The loop runs once. */
@media (prefers-reduced-motion: reduce) {
.pulse {
animation-duration: 0.01ms;
animation-iteration-count: 1;
}
}prefers-reduced-motion: no-preference, so the element is simply shown. scrollReveal and scrollAnimate show the end state at once; for an exit that end is hidden, so the generated JavaScript leaves an exit off for those readers.CSS animation questions
How do I make a CSS animation?
Write the motion as @keyframes: a name, then the property values at 0% (from), 100% (to) and any stops between. Then attach it to an element with the animation property: animation: fade-up 600ms ease-out both; names the keyframes, the duration, the easing and the fill mode. The generator writes both, with a reduced-motion rule, for any of its 27 effects.
What is the difference between a CSS transition and a CSS animation?
A transition animates a property from its old value to its new one when the value changes, on :hover or when a class is added: two ends, once per change. An animation runs @keyframes on its own as soon as it applies, with as many stops as you like, a delay, a repeat count and a direction, and it can loop. A transition suits a change of state; an animation suits motion with a shape of its own.
How do I keep a CSS animation on its last frame?
Set animation-fill-mode: forwards, or both. With the default, none, the keyframes stop applying when the animation ends and the element snaps back to its own style. forwards holds the frame it ended on, which is the 100% frame unless the last cycle ran backwards: with alternate and an even count it is the 0% frame. backwards does the same for the first frame during the delay.
How do I loop a CSS animation with a pause between loops?
animation-iteration-count: infinite repeats it for as long as it applies, and alternate makes every other cycle run backwards. CSS has no delay between cycles: animation-delay waits once, before the first. Build the pause into the keyframes instead: move from 0% to 60% and hold the same values from 60% to 100%, so a 2s cycle moves for 1.2s and rests for 0.8s.
How do I restart a CSS animation with JavaScript?
Take the class off, make the browser notice, and put it back: remove the class, read el.offsetWidth, add the class again. Reading offsetWidth forces a layout between the two; without it the browser only ever sees the class still there, and nothing restarts. Changing animation-name restarts it too. The generator’s Replay button does the first.
const el = document.querySelector('.fade-up');
el.classList.remove('fade-up');
void el.offsetWidth; // a forced layout: the browser sees the class gone
el.classList.add('fade-up');How do I trigger a CSS animation on scroll?
In CSS, animation-timeline: view() ties the animation to the element’s place in its scroller, and animation-range picks the stretch, such as entry 0% cover 30%; scrolling drives it forwards and back. It runs in Chrome and Edge 115, Safari 26 and Firefox 159 or later; inside @supports, other browsers simply show the element. In JavaScript, svg-scroll-draw’s scrollReveal ties the same from and to to the scroll in every browser, or scrollAnimate for values scrollReveal’s from cannot hold. The generator writes both.
Why is my CSS animation not working?
Check, in this order: the name in animation matches the @keyframes name exactly; there is a duration, since without one it is zero and nothing shows; the element is not display: inline, which ignores transform (inline-block works); no later rule or animation shorthand overrides it; and the reader has not asked for less motion on a page that turns animation off then. Chrome’s DevTools Animations panel shows whether it ran at all.
Shape the easing first: the easing visualizer. All of svg-scroll-draw 2.23.3: the docs.