motion-curve
Animates each character of a sentence along a continuous sine-wave curve. Letters rotate to follow the tangent, giving the text a natural, fluid motion.
Preview
Import
Usage
Static content
Write the text as a child. The browser shows it as plain text before the element is defined, so the page never paints an empty gap while the library loads.
Dynamic content
Use the text attribute when text changes at runtime. Setting the property restarts the animation automatically.
If both are present, the attribute wins and the child text stays as the pre-upgrade fallback. For long marquee strings in loop mode, prefer the attribute: a long fallback would wrap across several lines before the element upgrades.
Loop mode
Add loop to scroll the text continuously like a ticker, with the wave applied as it travels. The text is duplicated for a seamless loop.
Loop gap
By default a 48px gap separates each repetition. Set loop-gap="0" for continuous text with no visible break.
Pause on hover
Add pause-on-hover to freeze the wave (and scroll in loop mode) when the cursor enters, resuming on leave.
How it works
Each character is wrapped in an inline-block span. After render, the component measures each span's pixel x-offset. A
requestAnimationFrame loop then computes a sine value and tangent angle for each character based on its x-position and the current phase, applying
translateY + rotate. The phase advances each frame, making the wave travel through the text.
The rotation is derived from the curve's tangent: angle = atan(A · k · cos(kx − phase)), so letters lean naturally into the curve rather
than staying upright.
Properties
| Attribute | Property | Type | Default | Description |
|---|---|---|---|---|
| | | string | — | The sentence to animate. Falls back to the child text when unset |
| | | number | 24 | Peak vertical offset in px |
| | | number | 280 | Horizontal distance of one full wave cycle in px |
| | | number | 0.4 | Wave cycles per second |
| | | boolean | false | Scroll the text in a seamless continuous loop |
| | | number | 80 | Horizontal scroll speed in px/s (loop mode only) |
| | | number | 48 | Space in px between the end of one loop repetition and the start of the next. Set to 0 for continuous text with no gap. |
| | | boolean | false | Remove the vertical padding added to accommodate the wave. |
| | | boolean | false | Pause the wave and scroll on mouseenter, resume on mouseleave. |
JavaScript API
Control the curve programmatically with play(), pause(), finish(), and cancel().
Accessibility
When prefers-reduced-motion is set, the animation does not start and the text renders flat.