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
beforeafterdelaydurationiteration-count: 3123100%0%0600130020002700msanimation-fill-modenoneforwardsbackwardsboth
Fig. 1 — delay 600 ms, duration 700 ms, 3 cycles, alternate, fill both: the keyframe offset over time. Below, where each fill mode applies the keyframes.
01The tool

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.css24 lines
/* 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.
02The model

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.

fade-up.css4 lines
/* The generator's default: fade up. */
.fade-up {
  animation: fade-up 600ms ease-out both;
}
01

animation-name

none by default. Which @keyframes to run. Nothing runs without it.

02

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.

03

animation-timing-function

ease by default. The easing, applied to each stretch between two keyframes, not once across the whole cycle.

04

animation-delay

0s by default. How long to wait before the first cycle. A negative delay starts part of the way through.

05

animation-iteration-count

1 by default. How many cycles: a number, fractions included, or infinite.

06

animation-direction

normal by default. Which way each cycle runs: normal, reverse, or alternate and alternate-reverse, which turn round every cycle.

07

animation-fill-mode

none by default. Whether the keyframes apply outside the cycles: backwards during the delay, forwards after the end, both for both.

08

animation-play-state

running by default. paused holds the animation where it is; running carries on from there.

Per keyframe
The timing function runs again between every pair of keyframes, so 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.
0327 effects

@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:

heartbeat.css11 lines
@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.

EffectWhat it doesStopsOn scroll in JS
Entrances · From somewhere else to where the element is.
fade-inOpacity from 0 to 1, nothing moves.2scrollReveal
fade-upFades in while rising 32px into place.2scrollReveal
fade-downFades in while dropping 32px into place.2scrollReveal
fade-leftFades in while moving 32px to the left, into place.2scrollReveal
fade-rightFades in while moving 32px to the right, into place.2scrollReveal
zoom-inGrows from 60% of its size as it fades in.2scrollReveal
flip-in-xTurns down into place about its horizontal axis, in perspective.2scrollAnimate
flip-in-yTurns into place about its vertical axis, in perspective.2scrollAnimate
slide-inSlides in from the left by its own width, fully opaque.2scrollAnimate
blur-inComes into focus from a 12px blur as it fades in.2scrollAnimate
rotate-inTurns 45° and grows from 80% as it fades in.2scrollReveal
bounce-inPops up past full size, settles back, then lands.5CSS only
drop-inFalls from 64px above, overshoots a little, and settles.4CSS only
Attention · Start and end where the element is; usually repeated.
pulseSwells to 106% and back, on a loop.3CSS only
shakeSide to side, each swing smaller than the last.8CSS only
wobbleSways and tilts side to side, dying away.7CSS only
heartbeatTwo quick beats, then a rest, on a loop.6CSS only
jelloSkews one way and the other, settling like a jelly.8CSS only
swingHangs from its top edge and swings to rest.6CSS only
floatDrifts 10px up and back down, slowly, on a loop.3CSS only
Exits · From where the element is to gone.
fade-outOpacity from 1 to 0, nothing moves.2scrollAnimate
fade-out-upFades out while rising 32px.2scrollAnimate
fade-out-downFades out while dropping 32px.2scrollAnimate
zoom-outShrinks to 60% of its size as it fades out.2scrollAnimate
flip-out-xTurns away about its horizontal axis, in perspective.2scrollAnimate
slide-outSlides out to the right by its own width, fully opaque.2scrollAnimate
blur-outGoes out of focus to a 12px blur as it fades out.2scrollAnimate
04On scroll

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
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.

05Less motion

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.

reduced-motion.css9 lines
/* 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;
  }
}
On scroll
The scroll-driven CSS only applies under 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.
067 questions

CSS animation questions

Q1

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.

Q2

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.

Q3

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.

Q4

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.

Q5

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.

restart.js5 lines
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');
Q6

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.

Q7

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.