Skip to content
← components

Pixelated Marquee

new

An infinite logo/content scroller that dissolves into pixels at the edges using an ordered Bayer dither mask — instead of the usual soft fade.

Orbit
Prism
Vertex
Loop
Quanta
Flux
Cobalt
Ember

Usage

example.tsx
import { PixelatedMarquee } from "~/components/marquee/pixelated-marquee";

<PixelatedMarquee duration={32} edgeWidth={120} pixelSize={8}>
  {logos.map((logo) => (
    <Logo key={logo.id} {...logo} />
  ))}
</PixelatedMarquee>

How the edge works

A normal marquee fades its edges with a linear-gradient mask. This one instead carves the edge into discrete pixel blocks using an ordered (Bayer) dither — the same trick old games used to fake transparency on hardware that only had on/off pixels.

For each cell along the edge, its horizontal progress toward the solid interior is compared to a threshold from a 4×4 Bayer matrix. If progress beats the threshold, the pixel stays; otherwise it's cut. The result is a crisp, retro dissolve that's rendered entirely with a CSS mask — no canvas, no JS per frame.

dither.ts
const BAYER_4X4 = [
  [0, 8, 2, 10],
  [12, 4, 14, 6],
  [3, 11, 1, 9],
  [15, 7, 13, 5],
];

// A cell is revealed when its distance from the edge
// exceeds its Bayer threshold -> crisp pixel dissolve.
const threshold = (BAYER_4X4[row][col % 4] + 0.5) / 16;
if (progress > threshold) drawPixel(col, row);

The mask is built once as an SVG data URI and tiled vertically, so the effect is essentially free at runtime and respects prefers-reduced-motion.

Props

proptypedefaultdescription
childrenReactNodeItems to scroll. Rendered twice for a seamless loop.
durationnumber32Loop duration in seconds. Lower is faster.
direction"left" | "right""left"Scroll direction.
gapnumber56Gap between items, in px.
edgeWidthnumber112Width of the dissolving edge region, in px.
pixelSizenumber8Size of each dissolve pixel block, in px.
pauseOnHoverbooleantruePause the scroll while hovered.