Gallery✨ Premium

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.

MarqueeCSS MaskPointerDual Layer

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

Art school hero: each studio's color under the mouse — Lens Marquee example 1

① Art school hero: each studio's color under the mouse

WhenThe full width of the top of the page: three rows of disciplines (design, photo, illustration, animation, motion, 3D, UX, architecture, textile, branding) in grey outline, the navigation on top and a tagline at the bottom.
WhyVisitors discover what the school teaches by “lighting up” the words themselves; color only appears where they look, which keeps the page restrained yet alive.
Settingsdata-lmq-speed="34", data-lmq-radius="96"; 540 × 360 scene, default font size (clamp(26px, 4.5vw, 50px)); navigation and tagline at z-index: 20 above the scene.
Animated background of an agency Careers page, lens sweeping on its own — Lens Marquee example 2

② Animated background of an agency Careers page, lens sweeping on its own

WhenBehind the Careers page content: the agency's job titles scroll in a very faint outline, a “6 open positions” card sits on the left and the lens passes by itself over the right side.
WhyThe content stays in front and readable; the background recalls the roles on offer and moves without asking for any action, even on touch screens.
Settingsdata-lmq-speed="30", data-lmq-radius="110"; dimmed outline (-webkit-text-stroke-color: rgba(255,255,255,.22)); a content layer covers the scene, so it gets no pointer events and keeps its automatic sweep; the bottom stays free for the Pause button.
Course keywords on a phone, the lens follows the finger — Lens Marquee example 3

③ Course keywords on a phone, the lens follows the finger

WhenOn the mobile page of a graphic design course, between the title and the duration: three small rows of keywords (logo, branding, poster, print, web, motion, portfolio…). A finger sliding over them brings out the color.
WhyOn a narrow screen, the lens turns a list of tags into a small touch game, without getting in the way of vertical page scrolling.
Settingsdata-lmq-speed="24", data-lmq-radius="40"; 110 px scene, 34 px rows, 21 px words; 22 px Pause button. For the capture, the scene sends “touch” pointer events (a simulated finger) to the real code; the white dot showing it belongs to the scene.

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 to tabindex="-1"). The scene is a region named by aria-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-y lets 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.

Chrome 64+✓ Full (measured)
Firefox 69+✓ Full (measured)
Safari 13.1+✓ Measured in WebKit, not in a real Safari
Edge 79+✓ Full (same engine as Chrome)
Safari iOS 13.4+✓ Not checked on a device
Chrome Android 64+✓ Finger lens measured in emulation

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):

index.html — structure
<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 / propertyDefaultEffect
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

So both layers always match: the script copies the outline layer into .lmq-fill-layer on start. Leave .lmq-fill-layer empty in your HTML.
Yes, with 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.
With no active pointer it slowly sweeps the scene (one horizontal loop takes about 18 seconds). It follows the finger during a tap or a horizontal drag; a vertical drag scrolls the page, as intended.