motion-curve

Animates each character of a sentence along a continuous sine-wave curve. Letters rotate to follow the tangent (the wave's direction at each point), giving the text a natural, fluid motion.

Preview

Make it wave.

Import

import 'motion-components/motion-curve'   // just this one
import 'motion-components'               // or the whole library

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.

<motion-curve amplitude="28" wave-length="260" wave-duration="2.5">
  Make it wave.
</motion-curve>

Dynamic content

Use the text attribute when text changes at runtime. Setting the property restarts the animation automatically.

const el = document.querySelector('motion-curve')
el.text = 'UPDATED'   // animation restarts

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.

<motion-curve
  text="Make it wave · Physics-based · Zero CSS · "
  loop
  amplitude="22"
  wave-length="320"
  wave-duration="3.33"
  loop-speed="70"
></motion-curve>

Loop gap

By default a 48px gap separates each repetition. Set loop-gap="0" for continuous text with no visible break.

<motion-curve
  text="Spring physics · No CSS · Framework-free · "
  loop
  loop-gap="0"
></motion-curve>

Pause on hover

Add pause-on-hover to freeze the wave (and scroll in loop mode) when the cursor enters, resuming on leave.

<motion-curve
  text="Make it wave · Hover to pause · Zero CSS · "
  loop
  pause-on-hover
></motion-curve>

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
text text string — The sentence to animate. Falls back to the child text when unset
amplitude amplitude number 24 Peak vertical offset in px
wave-length waveLength number 280 Horizontal distance of one full wave cycle in px
wave-duration waveDuration number 2.5 Seconds for one full wave cycle. Lower is faster, 0 holds the wave still
loop loop boolean false Scroll the text in a seamless continuous loop
loop-speed loopSpeed number 80 Horizontal scroll speed in px/s (loop mode only)
loop-gap loopGap 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.
no-pad noPad boolean false Remove the vertical padding added to accommodate the wave.
pause-on-hover pauseOnHover boolean false Pause the wave and scroll on mouseenter, resume on mouseleave.

JavaScript API

Control the curve programmatically with play(), pause(), finish(), and cancel().

playState: –

Accessibility

When prefers-reduced-motion is set, the animation does not start and the text renders flat.