Lens Marquee
Rows of outlined words scroll by; a round lens that follows the mouse or finger reveals the same sentence beneath in a colorful gradient.
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



How it works
You write your words once, in the outline layer .lmq-outline-layer: one .lmq-row-wrap per row, .lmq-item--a words and .lmq-sep--a separators. On start the script copies every row into .lmq-fill-layer, switching the classes to --b (text filled with a violet → pink → orange → yellow gradient), then adds in both layers the copies needed to cover the width.
Both layers move together at 36 px/s, rows 1 and 3 to the left, row 2 to the right. The gradient layer is cut by a radial-gradient mask with a 90 px radius centered on --lmq-x / --lmq-y: color only shows inside that circle, outlined by a glowing .lmq-ring. The lens catches up with the pointer (mouse, pen or finger) with a slight lag; without a pointer, it sweeps the scene on its own along a slow loop.
Hovering the scene with a mouse or moving focus into it pauses the rows; the lens still follows the cursor. The Pause button stops the rows and the automatic sweep. Each .fx-0835-root is independent, rebuilds when its width changes and stops off screen.
Accessibility
- Real text: your outline-layer words are read once; the gradient layer and the loop copies carry
aria-hidden="true"(links set totabindex="-1"). The scene is a region named byaria-label. - Pause: hover and keyboard focus stop the rows; the Pause button (
aria-pressed) freezes them along with the sweep (WCAG 2.2.2). - Reduced motion: under
prefers-reduced-motion: reduce, no copies and no scrolling: each row is scrolled by hand (finger, wheel, keyboard) and the colored layer follows the same position. The lens no longer sweeps on its own; it jumps straight under the pointer, without lag. The Pause button is hidden. - Touch:
touch-action: pan-ylets the page scroll when the finger moves up or down; a tap or a horizontal drag puts the lens under the finger.
Browser compatibility
mask-image with a radial gradient and CSS variables (-webkit- prefix included), background-clip: text, Pointer Events, IntersectionObserver and ResizeObserver (feature-tested). Measured on 2026-11-08 in Chromium, Firefox and Playwright's WebKit: 36 px/s, pause on hover, focus and button, two independent copies, still lens under reduced motion, page still scrolling under a finger at 390 px.
Without CSS masks, the gradient layer shows everywhere (no lens, but colored text). Without JavaScript, only the outline layer shows, still.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0835-root" data-lmq-speed="36" data-lmq-radius="90">
<div class="lmq-stage" role="region" aria-label="…">
<div class="lmq-outline-layer">
<div class="lmq-row-wrap">
<div class="lmq-track">
<span class="lmq-item--a">…</span><span class="lmq-sep--a" aria-hidden="true">·</span>
</div>
</div>
<!-- 2 more .lmq-row-wrap -->
</div>
<div class="lmq-fill-layer" aria-hidden="true"></div>
<div class="lmq-ring" aria-hidden="true"></div>
<button class="lmq-pause-btn" type="button" aria-label="…" aria-pressed="false">⏸</button>
</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 |
|---|---|---|
Words (HTML — .lmq-item--a in each .lmq-track of the outline layer) |
3 rows of 5 words | Write them once: the gradient layer is copied by the script. Links work (<a class="lmq-item--a">). |
Speed (HTML — data-lmq-speed on .fx-0835-root) |
36 (px/s) | From 0 to 600; alternating directions left, right, left. |
Lens radius (HTML — data-lmq-radius) |
90 (px) | From 20 to 400; the .lmq-ring takes the same size. |
Revealed gradient (CSS — background of .lmq-item--b) |
#a855f7 → #ec4899 → #f97316 → #eab308 | Any gradient or a flat color (keep background-clip: text). |
Outline (CSS — -webkit-text-stroke of .lmq-item--a) |
1.5 px rgba(255,255,255,.42) | Fainter for a background, stronger for a headline. |
Size (CSS — item font-size, .lmq-row-wrap height) |
clamp(26px, 4.5vw, 50px); 78 px (60 px below 520 px) | Change both together to keep the rows aligned. |
Ring (CSS — .lmq-ring) |
Violet border and glow | Change border and box-shadow, or hide it for a borderless lens. |
FAQ
.lmq-fill-layer on start. Leave .lmq-fill-layer empty in your HTML.position: absolute and a z-index above 10. Content laid on top takes the clicks instead of the scene: under that content, the lens stops following the mouse and keeps sweeping on its own.