Gallery✨ Premium

Text-Path Marquee

Up to three SVG text lines glide on rippling waves; the pointer lifts a bump under it and a drag moves the text by hand.

MarqueeSVG textPathWavePointer

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Gallery has 44 effects, including 6 free. Explore the category →

3 usage examples

Electronic festival line-up that ripples under the mouse — Text-Path Marquee example 1

① Electronic festival line-up that ripples under the mouse

WhenThe whole top of a festival page: artist names in the middle, dates in outline above, the festival name in violet below; the navigation at the top and the ticket button at the bottom sit on the scene.
WhyThe three lines give volume to a plain list of names. When the mouse passes, the text stops so it can be read and the wave rises under the cursor: visitors feel they are touching the poster.
Settingsdata-tpm-speed="70", data-tpm-amp="26", data-tpm-bump="40"; 540 × 360 scene without rounded corners, violet gradient background, .tpm-fade matching the background, bottom track in #e879f9.
Today's lesson times rippling at the foot of a surf school's page — Text-Path Marquee example 2

② Today's lesson times rippling at the foot of a surf school's page

WhenUnder the home grid of a surf shop and school (boards, today's lessons, today's swell), an 88 px teal band ripples the day's slots: “9:00 ~ 11:00 ~ 14:30 ~ 17:00”. The “Today's lessons” tile points to it.
WhyFour times fit on one line that echoes the wave without images; at that height the motion stays quiet, and hovering stops the line so a slot can be read.
SettingsA single .tpm-path and a single <textPath>; data-tpm-speed="45", data-tpm-amp="10", data-tpm-bump="16"; 88 px high scene, background and .tpm-fade #0e4f5c, #ecfeff text at 30 units.
Bib number rippling in a trail race registration confirmation — Text-Path Marquee example 3

③ Bib number rippling in a trail race registration confirmation

WhenFinal screen of an online registration: green tick, “You're registered!”, then a panel where “N° 0427 ✓ ESQUERNES 42 K” runs on a wave, above the race details and the Add to my calendar button.
WhyThe success message gets a festive feel without confetti: the bib number, the one thing people need to remember, is what moves.
SettingsA single track; data-tpm-speed="40", data-tpm-amp="34", data-tpm-bump="30"; 208 × 100 px scene on #f2f7ef, green #2f6b3a text at 58 units (about 17 px on screen), light outline to lift it off the background.

How it works

The scene is an 800 × 300 unit SVG (preserveAspectRatio="xMidYMid slice": it fills the frame and crops what overflows). It holds up to three .tpm-path paths and as many <textPath>: the script gives each pair an id unique to the instance, reads your text from the <textPath> and repeats it up to four times the path length. Track 1 in the middle (y = 148), track 2 at the top (72), track 3 at the bottom (228).

Every 33 ms each path is rebuilt from 80 segments: a sine wave of amplitude 22 (×0.68 for the top and bottom tracks) whose phase drifts 0.42 radian per second and whose amplitude breathes ±20%. The text moves 60 px/s along the path, at relative speeds of 1, 0.58 and 1.22 for parallax. Under the pointer, a bell-shaped bump (32 units high, about 104 units wide) lifts the three lines and follows the cursor with a slight lag.

Hovering the scene with a mouse or moving focus into it stops the scrolling and the wave drift; the bump keeps following the pointer. Dragging horizontally (mouse or finger) moves the text forward or back by hand. The Pause button freezes everything. Each .fx-0833-root is independent and the loop stops off screen.

Accessibility

  • Real text: the repeated SVG carries aria-hidden="true"; the script copies each distinct sentence once into .tpm-sr, a visually hidden text that screen readers read. The scene has the marquee role and an aria-label to adapt.
  • Pause: hover and keyboard focus stop the scrolling; the Pause button (aria-pressed) stops everything until the next click (WCAG 2.2.2).
  • Reduced motion: under prefers-reduced-motion: reduce, no loop runs: the waves stay still and the Pause button is hidden. Horizontal dragging still works to read the rest of the text.
  • Touch: touch-action: pan-y lets the page scroll with a finger; only a horizontal gesture moves the text.

Browser compatibility

SVG <textPath> with the unprefixed href attribute, getTotalLength(), getComputedTextLength(), Pointer Events, IntersectionObserver (feature-tested). Measured on 2026-11-08 in Chromium, Firefox and Playwright's WebKit: 60 px/s scrolling, stop on hover, focus and button, two independent copies, reduced motion, page still scrolling under a finger at 390 px.

Chrome 56+✓ Full (measured)
Firefox 61+✓ Full (measured)
Safari 13+✓ Measured in WebKit, not in a real Safari
Edge 79+✓ Full (same engine as Chrome)
Safari iOS 13+✓ Not checked on a device
Chrome Android 56+✓ Touch measured in emulation (390 px)

Without JavaScript, the paths are empty and nothing shows in the SVG: provide a visible heading elsewhere if the text matters. Without CSS masks, the edge fade becomes a gradient laid on top.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0833-root" data-tpm-speed="60" data-tpm-amp="22" data-tpm-bump="32">
  <div class="tpm-scene" role="marquee" aria-label="…">
    <svg class="tpm-svg" viewBox="0 0 800 300" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <defs><path class="tpm-path" d=""/> ×3</defs>
      <text class="tpm-text-solid"><textPath>…</textPath></text>
      <text class="tpm-text-outline"><textPath>…</textPath></text>
      <text class="tpm-text-solid tpm-text-soft"><textPath>…</textPath></text>
    </svg>
    <span class="tpm-sr"></span>
    <div class="tpm-fade" aria-hidden="true"></div>
    <button class="tpm-pause-btn" type="button" aria-pressed="false"><span class="tpm-pause-icon" aria-hidden="true">⏸</span> Pause</button>
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Text (HTML — content of each <textPath>) “EFFECT.LABS — VANILLA EFFECTS — NO FRAMEWORK — ” on all three tracks One sentence per track, ending with a separator (“—”, “~”…) so the repetition reads well.
Number of tracks (HTML — .tpm-path / <text> pairs) 3 1, 2 or 3. With one, keep the middle track (y = 148).
Speed (HTML — data-tpm-speed on .fx-0833-root) 60 (px/s along the path) From 0 to 600; the top and bottom tracks keep their relative speeds (0.58 and 1.22).
Amplitude (HTML — data-tpm-amp) 22 (SVG units) Wave height, from 0 (straight line) to 80.
Frequency (HTML — data-tpm-freq) 1.5 Number of waves across the SVG width, from 0.2 to 6.
Pointer bump (HTML — data-tpm-bump) 32 (SVG units) Height of the bump under the cursor; 0 removes it.
Colors and sizes (CSS — .tpm-text-solid, .tpm-text-outline, .tpm-text-soft, .tpm-fade) White 28, violet outline 26, violet 24; background #0a0a0f fill, stroke and font-size (in SVG units). Give .tpm-fade and the solid text's stroke your background color.

FAQ

The SVG scales with the scene: in a 200 px wide frame, one unit is about 0.3 px. Raise the font-size of the text classes (58 units give about 17 px in that case) and preferably keep a single track.
Yes, every <textPath> keeps its own text and its own repeat length. The text read by screen readers includes each distinct sentence once.
That is the hover pause meant for reading (WCAG 2.2.2). The bump under the cursor keeps reacting. To read further without waiting, drag the text horizontally.