Bezier Curve
A 22 px orange ball travels back and forth along a cubic Bézier arc in 2.5 s, slowing down at both ends, with both handles and their control points drawn in dotted lines: you see the curve and how it is built, and a ground shadow follows the ball, shrinking as it rises — CSS only, zero JavaScript, ball resting at the top when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. 3D has 50 effects, including 8 free. Explore the category →
3 usage examples



How it works
The .fx-0033-root root (background #0a0a0f, at least 300 px tall) centers a 300 × 220 px .mp-bezier-scene. An <svg> of the same size draws three things in faint lines: the dotted arc M30,190 C30,30 270,30 270,190, the two control points (30,30) and (270,30), and the two handles that link them to the ends of the arc. On top sit two divs: .mp-bezier-shadow, the shadow, and .mp-bezier-ball, the ball (#f59e0b → #f97316 gradient and an orange glow).
The ball gets offset-path: path("M 30 190 C 30 30 270 30 270 190"), the same path as the svg, and an animation that takes offset-distance from 0 to 100% in 2.5 s, ease-in-out, alternate: it goes, slows down, comes back. The browser places the center of the ball on the path point; the ball therefore has no negative margin, and its center stays on the drawn curve (measured gap: 0.2 px at most in Chromium 151, Firefox 153 and WebKit 26, with identical positions in all three). The path matches everywhere because the svg is not stretched: 300 × 220 px for a 300 × 220 viewBox.
The shadow is a 32 × 8 px orange ellipse placed under the ends of the arc. It cannot follow the ball with a second offset-path: offset-distance measures the distance traveled along the curve, not the horizontal position, and the ball rises almost vertically at first. Its fx0033Shadow keyframes therefore give, every 5% of the time, the horizontal position of the ball already computed on the curve, with the ease-in-out easing baked in; the shadow animation is linear, with the same duration and alternate. Measured gap between the ball and its shadow: 0.1 px. At the same time, the shadow goes from scale 1 and 90% opacity on the ground to 0.5 and 40% at the top: that is what conveys height.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0033-rootstops the ball at the top of the curve (offset-distance: 50%), between the two handles, with the shadow right below. Measured: 0% of pixels change over 0.8 s in Chromium, Firefox and WebKit, versus 1.2% in normal mode. - Decorative content: the scene carries no text; the svg and the empty
divs are ignored by screen readers. If the curve explains something (a drawing tool, a sports move), write the explanation next to it, as in the examples above. - Contrast: the orange ball reads well on the dark background; the path and handles are deliberately faint (12% and 30% opacity). For a lesson where the construction matters, make them stronger (see "Customize").
- Rhythm: one trip in 2.5 s, no flashing; the motion stays within a 300 × 220 px frame.
Browser compatibility
CSS only: offset-path: path(), animated offset-distance and a transform animation for the shadow, no JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26 (Safari's engine): same ball positions to within half a pixel, same shadow, same stop under reduced motion. Not checked on a real Safari.
A browser without offset-path leaves the ball still at the top left of the scene; the path, the handles and the shadow (which keeps sliding along the ground) stay visible.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0033-root">
<div class="mp-bezier-scene">
<svg viewBox="0 0 300 220" fill="none">
<path d="M30,190 C30,30 270,30 270,190" … /> <!-- the curve -->
<circle … /> <circle … /> <!-- control points -->
<line … /> <line … /> <!-- handles -->
</svg>
<div class="mp-bezier-shadow"></div>
<div class="mp-bezier-ball"></div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Ball color (CSS — .mp-bezier-ball) |
#f59e0b → #f97316 gradient, orange glow | Change the background and the box-shadow color. On a light background, replace the glow with a small drop shadow. |
Ground shadow (CSS — .mp-bezier-shadow) |
32 × 8 px orange ellipse | On a light background, switch the gradient to dark brown or gray (rgba(60,45,25,.8)): a black shadow vanishes on a black background, hence the default orange. To remove it, delete the div and its rule. |
Speed (CSS — 2.5s in both animation declarations) |
2.5 s per trip | Change the ball AND shadow durations together, otherwise the shadow drifts. 1.5 s gives a quick throw, 4 s a slow demonstration. |
Handles and control points (HTML — svg circle and line) |
3 px points at 30%, handles at 8% opacity | For a lesson, raise the opacity or recolor them; for plain decoration, hide them in CSS (display: none): only the dotted curve remains. |
| Curve shape (HTML + CSS) | M30,190 C30,30 270,30 270,190 | The path is written three times: the svg path d, the ball's offset-path, and the shadow keyframes, computed for this curve. If you change the curve, change the first two and recompute the keyframes (horizontal position of the ball every 5% of the time), or remove the shadow. |
Background (CSS — .fx-0033-root) |
#0a0a0f | Change or remove the root background to set the scene on your page; then check the path and shadow colors. |
FAQ
offset-distance would drift by 27 px at the worst moment. The 21 keyframes give the real horizontal position of the ball, easing included; measured, the shadow stays within 0.1 px under the ball. The cost: if you change the curve or the easing, they must be recomputed.animation-direction: alternate: every other trip plays in reverse, and the ease-in-out easing makes the ball almost stop at each end. It is the back-and-forth of a ball tossed between two people. With normal, the ball would jump from the end back to the start on every loop: add a fade then, or use a closed path.stroke-dashoffset; Bounce Path chains five lower and lower bounces. To explain a curve, a move or a throwing height, use Bezier Curve.