---
title: "Building a pixel-dissolve marquee"
description: "Why I replaced the soft gradient fade on a logo marquee with edges that disintegrate into graduated pixel blocks: bigger, blurrier, and fainter the further out they go."
date: "2026-01-09"
tags: ["css", "backdrop-filter", "pixelation", "frontend"]
---

Every logo marquee on the internet fades its edges the same way: a
`linear-gradient` mask that ramps opacity to zero. It's fine. It's also
everywhere. I wanted the edge to actually feel like something, so I made the
content disintegrate into pixels instead.

    {DEMO_LOGOS}

## One ramp isn't enough

A gradient fade changes exactly one thing across the edge: opacity. The result
is soft, but it's flat. The content just gets quieter. Real disintegration
changes a few things at once. As a logo travels toward the edge it should get
chunkier, blurrier, and fainter, all together.

So instead of a single mask, I overlay a stack of vertical bands on each edge.
Each band is a plain absolutely-positioned `div`, and the further out it sits,
the harder it dissolves whatever's behind it.

## Three ramps, one band

For each band I compute its outwardness `t` (1 at the outermost slice, trending
to 0 at the solid interior) and drive three properties off it.

```ts
const t = 1 - (k + 0.5) / bands; // 1 (outer) → 0 (inner)

const blur = t * maxBlur;                  // backdrop blur grows
const pixel = base * 2 ** Math.floor(t * 4); // pixel blocks grow
const fadePct = Math.pow(t, 1.35) * 96;    // fades into the surface
```

The `pow(t, 1.35)` curve on the fade keeps the inner bands nearly solid and
makes the falloff bite right at the edge, so content stays legible until it's
genuinely on its way out.

## Where the "pixels" come from

The blocky look is cheap. Each band paints a 1px mortar grid over its blurred
backdrop, and the grid cell size *is* the band's `pixel` value.

```ts
band.style.backdropFilter = `blur(${blur}px)`;
band.style.backgroundColor =
  `color-mix(in srgb, ${surface} ${fadePct}%, transparent)`;
band.style.backgroundImage =
  `linear-gradient(${line} 1px, transparent 1px),
   linear-gradient(90deg, ${line} 1px, transparent 1px)`;
band.style.backgroundSize = `${pixel}px ${pixel}px`;
```

`backdrop-filter` blurs whatever's scrolling behind the band, and the grid chops
that blur into cells. Since each band has its own cell size, the blocks visibly
grow as you move outward: small near the content, coarse at the edge.
`color-mix` handles the fade without needing the surface color pre-split into an
rgba.

## Keeping the grid seamless

The trap with stacked bands is the lattice jumping phase between zones, which
reads as broken squares or stray lines. Two invariants fix it. Each band's
column width is a whole multiple of the largest cell, and the pixel sizes are a
doubling sequence (`base`, `2·base`, `4·base`, and so on). So a coarse band's
grid lines always land exactly on the finer band's lattice, and the squares
stay square all the way out.

The thing I'd carry to other effects: a convincing dissolve ramps several
properties together, not just opacity. The mortar-grid-over-`backdrop-filter`
trick fakes pixelation with no per-frame work, the bands compute once with
`useMemo`, and passing the real `surface` color is what lets the edges melt into
the actual backdrop instead of a guess.

The full, prop-driven component lives on the
[Pixelated Marquee](/components/pixelated-marquee) page. Drag the sliders to feel
how `pixelSize`, `maxBlur`, and `edgeWidth` change the dissolve.
