CSSanimation-timing-function & delay

animation-timing-function & animation-delay

animation-timing-function controls the speed curve of an animation (ease, ease-in, ease-out, linear, cubic-bezier, steps). animation-delay controls when the animation starts. Together they control the feel and timing of animations. Use delays for staggered effects; choose timing functions that match your animation's purpose (bouncy, smooth, mechanical, etc.).

Timing function types

Function

Feel

Use Case

linear

Constant speed

Continuous, mechanical motion

ease (default)

Slow start/end, fast middle

General purpose animations

ease-in

Slow start, then fast

Elements entering/appearing

ease-out

Fast start, then slow

Elements exiting/disappearing

ease-in-out

Slow start/end

Smooth, elegant transitions

cubic-bezier()

Custom

Precise control

steps()

Discrete steps

Sprite animations, frame-by-frame

CSS
/* Linear: constant speed */
@keyframes linear-motion {
  from { transform: translateX(0); }
  to { transform: translateX(200px); }
}

.linear-element {
  animation: linear-motion 2s linear;
  /* Moves at same speed throughout */
}

/* Ease (default): slow start, fast middle, slow end */
@keyframes ease-motion {
  from { transform: translateX(0); }
  to { transform: translateX(200px); }
}

.ease-element {
  animation: ease-motion 2s ease;
  /* Natural acceleration/deceleration */
}

/* Ease-in: slow start, fast end */
@keyframes ease-in-motion {
  from { opacity: 0; }
  to { opacity: 1; }
}

.ease-in-element {
  animation: ease-in-motion 1s ease-in;
  /* Gradually picks up speed */
}

/* Ease-out: fast start, slow end */
@keyframes ease-out-motion {
  from { opacity: 1; }
  to { opacity: 0; }
}

.ease-out-element {
  animation: ease-out-motion 1s ease-out;
  /* Gradually slows down */
}

/* Ease-in-out: slow start and end, fast middle */
@keyframes ease-in-out-motion {
  from { transform: translateY(0); }
  to { transform: translateY(100px); }
}

.ease-in-out-element {
  animation: ease-in-out-motion 1.5s ease-in-out;
  /* Smooth, elegant acceleration */
}

/* Cubic-bezier: custom timing */
@keyframes custom-motion {
  from { transform: scale(1); }
  to { transform: scale(1.5); }
}

.custom-element {
  animation: custom-motion 2s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Custom bouncy effect */
}

/* Steps: frame-by-frame animation */
@keyframes step-animation {
  0% { background-position: 0 0; }
  100% { background-position: 100% 0; }
}

.sprite-animation {
  animation: step-animation 1s steps(12, end);
  /* 12 discrete frames */
}
Cubic-bezier deep dive

CSS
/* cubic-bezier(x1, y1, x2, y2) */
/* Defines control points of a bezier curve */

/* Predefined timing functions as cubic-bezier equivalents */
.ease { animation: move 1s cubic-bezier(0.25, 0.1, 0.25, 1); }
.ease-in { animation: move 1s cubic-bezier(0.42, 0, 1, 1); }
.ease-out { animation: move 1s cubic-bezier(0, 0, 0.58, 1); }
.ease-in-out { animation: move 1s cubic-bezier(0.42, 0, 0.58, 1); }

/* Common custom curves */

/* Bouncy effect: y values exceed 0-1 range */
.bouncy {
  animation: bounce 1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Overshoots then returns */
}

/* Snappy: fast middle */
.snappy {
  animation: change 0.5s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}

/* Smooth: gentle curves */
.smooth {
  animation: move 2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Sharp: quick acceleration */
.sharp {
  animation: enter 0.8s cubic-bezier(0.3, 0, 0.8, 0.15);
}

/* Tools for creating cubic-bezier */
/* Use cubic-bezier.com or DevTools to visualize */

/* Browser DevTools can animate cubic-bezier curves */
.test-timing {
  animation: test 3s cubic-bezier(0.1, 0.7, 0.9, 0.2);
  /* Adjust in DevTools to see effect in real-time */
}
animation-delay for staggered effects

CSS
/* Single animation with delay */
.delayed-element {
  opacity: 0;
  animation: fade-in 0.6s ease forwards;
  animation-delay: 0.3s;
  /* Waits 0.3s, then fades in */
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Staggered list animations */
.list-item {
  opacity: 0;
  transform: translateY(20px);
  animation: slide-up 0.5s ease forwards;
}

.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:nth-child(4) { animation-delay: 0.3s; }
.list-item:nth-child(5) { animation-delay: 0.4s; }

@keyframes slide-up {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

/* Dynamic delay with CSS custom properties */
.dynamic-item {
  animation: slide-up 0.5s ease forwards;
  animation-delay: var(--index) * 0.1s;
  /* Calculated based on item position */
}

/* Multiple animations with different delays */
.complex {
  animation:
    fade 0.5s ease forwards,
    slide 0.5s ease 0.2s forwards,
    scale 0.5s ease 0.4s forwards;
  /* Each animation starts at different time */
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide { from { transform: translateX(-50px); } to { transform: translateX(0); } }
@keyframes scale { from { transform: scale(0.8); } to { transform: scale(1); } }
Steps timing function

CSS
/* steps(number-of-steps, direction) */
/* Divides animation into discrete steps instead of smooth curve */

/* 4-step animation */
.four-step {
  animation: move 2s steps(4, end);
  /* Animation moves in 4 discrete jumps */
}

/* 12-step sprite animation */
.sprite {
  width: 50px;
  height: 50px;
  background: url('sprite.png') 0 0;
  animation: animate-sprite 2s steps(12, end) infinite;
}

@keyframes animate-sprite {
  from { background-position: 0 0; }
  to { background-position: 600px 0; }
}

/* steps(n, start) vs steps(n, end) */
.steps-end {
  animation: blink 1s steps(2, end);
  /* Changes at end of each step */
}

.steps-start {
  animation: blink 1s steps(2, start);
  /* Changes at start of each step */
}

@keyframes blink {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Jump-start effect */
.jump-start {
  animation: move 1s steps(5, start);
  /* First frame shows immediately */
}

/* Jump-end effect (default) */
.jump-end {
  animation: move 1s steps(5, end);
  /* First frame waits for first step */
}

/* Film strip animation */
@keyframes film-strip {
  from { background-position: 0 0; }
  to { background-position: -2400px 0; }
}

.film {
  width: 200px;
  height: 100px;
  background: url('film.png') 0 0;
  animation: film-strip 3s steps(12, end) infinite;
  /* Shows 12 frames in 3 seconds */
}
Practical timing examples

CSS
/* Loading spinner: linear and infinite */
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.spinner {
  animation: spin 1s linear infinite;
  /* Constant rotation speed */
}

/* Bouncy entrance */
@keyframes bounce-in {
  0% { transform: scale(0); opacity: 0; }
  70% { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(1); }
}

.bounce-element {
  animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Bouncy overshoot effect */
}

/* Smooth fade-in */
@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.smooth-fade {
  animation: fade-in 1s ease-in-out;
  animation-delay: 0.2s;
}

/* Typing effect with steps */
@keyframes typing {
  from { width: 0; }
  to { width: 100%; }
}

.typing-text {
  max-width: 300px;
  overflow: hidden;
  white-space: nowrap;
  border-right: 3px solid #333;
  animation: typing 3s steps(30, end);
  /* Types 30 characters over 3 seconds */
}

/* Pulsing animation with ease-in-out */
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.6; transform: scale(1.05); }
}

.pulse-element {
  animation: pulse 2s ease-in-out infinite;
  /* Smooth breathing effect */
}

/* Staggered entrance */
.card {
  opacity: 0;
  transform: translateY(30px);
  animation: slide-up 0.6s ease-out forwards;
}

.card:nth-child(1) { animation-delay: 0s; }
.card:nth-child(2) { animation-delay: 0.2s; }
.card:nth-child(3) { animation-delay: 0.4s; }

@keyframes slide-up {
  to { opacity: 1; transform: translateY(0); }
}
Performance and optimization

CSS
/* GPU-accelerated properties animate smoother */
.good-animation {
  animation: move 2s ease-in-out;
}

@keyframes move {
  from { transform: translateX(0); }
  to { transform: translateX(100px); }
}

/* Avoid animating layout properties */
.bad-animation {
  animation: bad-move 2s ease-in-out;
}

@keyframes bad-move {
  from { left: 0; }
  to { left: 100px; }
}

/* Use will-change for complex animations */
.optimized {
  will-change: transform;
  animation: rotate 10s linear infinite;
}

/* Keep timing functions simple */
.simple-timing {
  animation-timing-function: ease-in-out;
}

.complex-timing {
  animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
  /* More complex curves use more CPU */
}

/* Prefers reduced motion */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-delay: 0 !important;
  }
}
Note
Timing functions control animation feel: `linear` for mechanical motion, `ease` for natural feel, `ease-in` for entrances, `ease-out` for exits, `cubic-bezier()` for custom curves. `steps()` creates frame-by-frame animations. Use `animation-delay` for staggered effects with 0.1s increments. Combine timing function with delay for complex entrance effects. Always test on real devices; timing feels different at various frame rates.
Next
Animation iteration and direction: [animation-iteration-count & direction](/css/animation-iteration).