Home / Snippets / Animation /

Bezier curve path

Move an element along a custom cubic bezier curve using CSS offset-path, with the element rotating to face its direction of travel.

New feature
animationno-js

Quick implementation

.curve-element {
  offset-path: path('M 40,120 C 80,20 200,200 260,80');
  offset-distance: 0%;
  offset-rotate: auto;
  animation: curve-travel 2s ease-in-out infinite alternate;
}

@keyframes curve-travel {
  to { offset-distance: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .curve-element { animation: none; }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building CSS motion path animations.

Goal: Move an element along a custom cubic bezier S-curve using CSS
offset-path — the element should visibly rotate to face its direction
of travel, and retrace its path in reverse. No JavaScript required.

Technical constraints:
- Use offset-path: path('M...C...') with a single cubic bezier segment
  to define the S-curve shape.
- Animate offset-distance from 0% to 100% with @keyframes, using the
  alternate fill mode so the element reverses back along the same path.
- Add offset-rotate: auto so the element faces its direction of travel.
- Use ease-in-out animation timing so the element decelerates at each end.
- Use oklch() for all color values — no hex or rgba.
- Wrap the animation in @media (prefers-reduced-motion: reduce) and stop it.

Framework variant (pick one):
A) Vanilla CSS — a .curve-element class applied to any positioned element.
B) React component — accepts pathD (string, the SVG path data), duration
   (number, seconds), color (string), and reverse (boolean) props.

Edge cases to handle:
- The path coordinates are in the element's local coordinate space —
  position the container as position: relative and the animated element
  as position: absolute with top: 0; left: 0.
- offset-rotate: auto may cause the element to flip 180 degrees when
  animating in the alternate direction; clamp with offset-rotate: auto 0deg
  or switch to offset-rotate: 0deg if upright orientation is required.
- For very short paths the ease-in-out timing may feel abrupt — consider
  linear with a custom linear() easing for fine-grained control.

Return CSS only (or a React component if variant B).

Why CSS motion paths replace JS animation libraries

CSS motion paths bring animator-quality curved movement to CSS with zero JavaScript. Before offset-path, achieving smooth curved movement required either SVG SMIL animation (deprecated in Chrome) or a JS library computing position at every requestAnimationFrame tick. Both approaches add complexity, script weight, or browser-compatibility caveats that offset-path sidesteps entirely.

The alternate fill mode on the animation property makes the element retrace its path back to the start — creating a natural pendulum-like motion along the curve without duplicating keyframes. Paired with ease-in-out timing, the element accelerates smoothly into the curve and decelerates as it approaches each endpoint.

Reading the path string: control points and endpoints

The path() value uses the same syntax as the SVG d attribute. A cubic bezier segment C x1,y1 x2,y2 x,y takes two control points — the handles that "pull" the curve — and an endpoint. In the S-curve example, M 40,120 sets the start point; C 80,20 200,200 260,80 defines control point 1 at (80, 20), control point 2 at (200, 200), and the endpoint at (260, 80). The two control points on opposite sides of the baseline create the characteristic S shape.

offset-rotate: auto computes the tangent angle at each point on the path and rotates the element to match, giving a natural "following the road" feel. You can see this most clearly with a non-circular element like a small rectangle or arrow — it banks into curves just as a car would. Use offset-rotate: 0deg to lock the element upright if rotation is unwanted.

Accessibility and performance

Always honour prefers-reduced-motion and stop the animation — curved motion is particularly disorienting for users with vestibular disorders. Setting offset-distance: 50% in the reduced-motion block parks the element at the midpoint of the curve rather than hiding it.

offset-distance is a composited property — the browser handles it on the GPU compositor thread, avoiding main-thread jank. The animation runs at 60 fps without triggering layout or paint. Keep the element's other properties (size, background colour) stable during the animation to prevent accidental repaints from undermining the compositing benefit.