# Pixel Sort Drag

> A WebGL pixel sort you paint with the pointer: drag across a photograph and the pixels in your wake smear into luminance-sorted streaks, fine on a slow move and torn wide open on a fast flick, then relax back into the clean image.

Canonical: https://gsapvault.com/effects/pixel-sort-drag
Live demo: https://gsapvault.com/demos/pixel-sort-drag/index.html

| Property | Value |
|----------|-------|
| Type | effect |
| Tier | paid |
| Price | £10 |
| Difficulty | advanced |
| Plugins | Core GSAP only |
| Techniques | webgl-shader, glitch, velocity-reactive, pointer-effects, hover-effect, keyboard-navigation |
| Uses Lenis | No |

## Lighthouse, as measured

Google Lighthouse on the demo, 28 September 2026. A measurement of the demo as shipped, not a promise for your page.

| Category | Score |
|----------|-------|
| Accessibility | 100 |
| Best practices | 100 |

No performance score is published: it depends on the deployment (server compression, caching, CDN, connection and device) rather than on the code, so measure it where it will live.

## Overview

The glitch-art pixel sort, as something you do with your hand rather than a filter you export. Drag across the photograph and a soft band follows the path; inside it, runs of pixels are re-ordered by brightness, so the image smears into dark-to-bright streaks that trail behind the direction you moved. Drag sideways and the rows sort; drag up or down and the columns do.

Pointer speed decides how much of the image counts as sortable. A slow, careful move sorts only a slice of the tones in short runs, leaving fine controlled streaks; a fast flick opens the threshold to almost everything and the band blows wide. Hold the drag and the stroke stays sorted. Let go and it relaxes: the threshold closes from the shadows up and the runs retract back into the photograph over about a second, with a faint streak lingering on the brightest highlights last.

It works on any photograph you give it, because the threshold adapts to that image's own tonal range, and it falls back to the plain photograph without JavaScript, without WebGL, or under reduced motion.

## Features

- A real luminance sort, not a displacement: every streak is made of the photograph's own pixels, re-ordered darkest to brightest along the run
- Rows or columns, chosen by the direction you drag, with the bright end of each run leading the stroke
- Pointer speed opens the sort threshold, widens the band and lengthens the runs, so slow moves stay fine and a flick tears the frame open
- The band is painted into a trail that follows the actual pointer path, and holds for as long as the drag does
- An eased release: the threshold closes from the shadows up so the streaks retract into the image rather than fading over it
- A faint residual streak on the brightest highlights that outlasts the band
- Threshold measured from each photograph's own tonal range, so a low-key portrait and a bright landscape sort with the same settings
- Touch, a slow autonomous stroke between touches on phones, and arrow-key sweeps for the keyboard
- Draws nothing at rest: the loop sleeps once the image is clean, runs only on screen, and releases the WebGL context on teardown

## Use Cases

- Music, fashion and editorial landing pages that want one tactile, unmistakable hero moment
- Photographer and art-director portfolios where the image itself is the interaction
- Campaign and launch pages that borrow the glitch-art register without a video file
- Record sleeves, event posters and drop pages built around a single striking photograph

## Vibe-Code Ready Setup

This effect includes `START-HERE-AI.md`, a product-specific copy-paste setup prompt for Cursor, Claude Code, ChatGPT, GitHub Copilot, Windsurf, and other coding assistants. It tells the assistant to inspect the existing stack, integrate the supplied files, preserve the design, scope selectors, retain accessibility and responsive behaviour, add framework-appropriate GSAP cleanup, and report what it tested.

[How AI-assisted setup works](https://gsapvault.com/vibe-coding)

## How It Works

The photograph is drawn on a single full-bleed plane with three.js. A small, low-resolution trail texture follows the pointer: each frame it paints a soft stroke from the previous pointer position to the current one, recording how hard, how fast and in which direction you moved, and each frame it forgets a little, at a rate measured in real time so it behaves the same at 30, 60 and 120Hz.

Wherever that trail is present, the fragment shader performs a bounded pixel sort. It looks along the row, or the column for a vertical stroke, finds the stretch of pixels whose brightness falls inside the current threshold, and replaces each pixel with the value it would hold if that stretch were sorted by luminance. The threshold, the length of the stretch and the width of the band all come from the recorded speed, and as the trail fades the threshold closes, which is what makes the release a retraction rather than a cross-fade. Outside the trail the plane is a single untouched texture read.

GSAP handles the responsive and reduced-motion branching, the teardown, the fade-in and the played strokes (the intro pass and the keyboard sweeps). The trail, its decay and the sort run on the renderer's own frame loop.

## Documentation

How this effect works and how it goes into a page. The reference you use once you own the files (worked examples, events, the programmatic API, the class list) ships with the download.

### Quick Start

**1. Add to your HTML `<head>`:**

_Code snippet omitted: it ships with the download._

The inline script is not optional. The photograph in the markup is the
fallback for no JavaScript, no WebGL and reduced motion, and without this it
paints for the half second it takes three.js to arrive, then gets swapped for
the canvas. The probe runs before first paint, stamps `html.gl` when a canvas
is coming, and the stylesheet hides the fallback under that class. No
JavaScript, no class, photograph shows; the effect removes the class again if
it cannot build a renderer after all. It probes WebGL2 because that is what
current three.js requires.

**2. Add to your `<body>`:**

_Code snippet omitted: it ships with the download._

**3. Add before the closing `</body>` tag:**

_Code snippet omitted: it ships with the download._

The script reads the `<img>`, drops a canvas in front of it, and hides the
figure once the canvas is actually there.

**Why the import map rather than a plain `<script src>` for three.js:** three
ships as ES modules only, and its old UMD build logs a deprecation warning on
every page load. The shim above imports it as a module, puts it on `window`,
and then loads `assets/script.js` as an ordinary script, so the effect itself
stays a plain file you can drop into any build (or none).

**Already using three.js as a module?** Then skip the shim, and just make sure
`window.THREE` is set before `assets/script.js` runs:

_Code snippet omitted: it ships with the download._

---

### Choosing Your Images

**This effect has a real content requirement, and ignoring it is the main way
to make it look broken.** A pixel sort re-orders the pixels along a row by
brightness, so it only shows where brightness *changes* along that row. A
flat sky sorted is the same flat sky. So:

- **Tonal variety across the frame, not just contrast.** A subject with dark
  masses (hair, silhouettes, clothing) against mid-tone gradients (gel light,
  a sunset, skin, painted walls) gives the sort something to drag through.
  Large uniform areas stay untouched no matter how fast you flick.
- **Strong structure perpendicular to the stroke.** Horizontal drags smear
  across vertical edges (a face in profile, trees, architecture) most
  visibly; vertical drags do the same to horizons and shelves.
- **Ungraded, or lightly graded.** The shader measures the photograph's own
  darkest and brightest tones and works inside that range, so a low-key
  image sorts fully, but a heavily crushed grade leaves little in the
  middle to re-order.

Test with a fast flick through the subject, not at rest. The fallback and
the canvas cover-fit the image, so any aspect ratio fills the frame.

The demo photograph is a derivative of
[Pexels photo 4985860](https://www.pexels.com/photo/a-woman-posing-in-blue-and-red-light-4985860/)
(Pexels licence), resized and encoded from `assets/img-src/` by
`assets/img-manifest.json`.

---

### Using It With Your Own Design

**What the effect needs from your markup:** an element with
`data-pixel-sort`, containing an element with `data-pixel-sort-source`
that holds one `<img>`. The script reads that image as its texture (no
second download) and uses its `alt` text as the canvas's accessible name.
Every `data-pixel-sort` on the page becomes its own instance.

**What is only the demo's styling:** the floating prompt pill, the dark page
ground, the crosshair cursor and the Mona Sans font. Delete them freely; the
effect draws nothing outside its own root.

**CSS the effect depends on:**

- **The container must have a real size.** The canvas is sized from the
  container's `clientWidth`/`clientHeight`, so `.pixel-sort` needs a height
  from somewhere: it is `position: absolute; inset: 0` in the demo. Give it
  a height of `0` and you get a canvas of `0`. It tracks later size changes
  on its own.
- The `.pixel-sort__source` figure must stay in the document. It is the
  fallback for no JavaScript, no WebGL and reduced motion, and it is hidden
  only by `html.gl` (the head probe) and `.pixel-sort.is-live`, a class the
  script adds *after* a canvas exists. Style it as the photograph it is.
- `touch-action: pan-y` on `.pixel-sort`. Sideways drags sort; vertical
  swipes keep scrolling the page on touch. If the effect is a full-screen
  hero that should own the whole gesture (so vertical drags sort columns on
  phones too), change it to `none`.
- `overflow: hidden` and `position: relative` or `absolute` on the root,
  so the canvas (`position: absolute; inset: 0`) sits inside it.

---

### Options

All options are data attributes on the `data-pixel-sort` element.

| Attribute | Values | Default | Description |
|-----------|--------|---------|-------------|
| `data-threshold` | `0` to `1` | `0.2` | Darkest tone that sorts on a slow move, as a fraction of the photograph's own range. Lower sorts more of the image even on gentle strokes. |
| `data-threshold-fast` | `0` to `1` | `0.02` | Darkest tone that sorts on a full-speed flick. |
| `data-band` | `0.02` to `0.3` | `0.075` | Radius of the band on a slow move, as a fraction of the height. |
| `data-spread` | `1` to `4` | `2.2` | How much a full-speed flick widens the band. `1` keeps it constant. |
| `data-reach` | `4` to `40` | `22` | Pixels between run samples at full speed. The longest streak is about 21 times this. |
| `data-speed-scale` | px per second | `1400` | Pointer speed that counts as a hard flick on a 1200px-wide effect (scaled with the width, so a phone needs a proportionally shorter flick). Raise it to make the band harder to blow open. |
| `data-split` | `0` to `3` | `1` | Red and blue channel delay along the streak at speed. `0` keeps colours registered. |
| `data-decay` | `0.9` to `0.995` | `0.972` | How much of the band survives each 60th of a second after release. Higher settles more slowly. |
| `data-hold` | `0.9` to `1` | `0.997` | The same while the pointer is held down. `1` keeps the whole stroke sorted until release. |
| `data-linger` | `0.9` to `0.999` | `0.99` | Decay of the residual highlight streak. |
| `data-highlight` | `0` to `1` | `0.72` | Tone above which the residual streak shows. |
| `data-residual` | `0` to `1` | `0.8` | Strength of the residual streak. `0` turns it off. |
| `data-intro` | `1`, `0` | `1` | One played stroke across the image when it loads. |
| `data-auto` | `1`, `0` | `1` | On touch devices, a slow autonomous stroke between touches. |
| `data-max-dpr` | number | `2` | Pixel ratio cap on fine pointers (touch is always capped at 1.5). |

#### Example: a calmer sort that needs real speed to open

_Code snippet omitted: it ships with the download._

#### Example: long, wild streaks that settle slowly

_Code snippet omitted: it ships with the download._

---

### Accessibility

- **Reduced motion**: the WebGL scene never starts. The photograph stays
  exactly as it is, full-bleed and still, with its `alt` text. Switching the
  preference while the page is open tears the canvas down and shows it.
- **Without JavaScript, or without WebGL**: the same photograph, for the same
  reason. One fallback, three failure modes, and it is never hidden until a
  canvas has actually been created.
- **Keyboard**: while the canvas is live the root is focusable, with a
  visible focus ring. The arrow keys play a sweep across the middle of the
  image in that direction, so the keyboard reaches both the row and the
  column sort.
- **Touch**: dragging a finger drives the same band a mouse does; between
  touches on phones a slow autonomous stroke keeps the image alive
  (`data-auto="0"` turns it off).
- **Screen readers**: the canvas carries `role="img"` and the photograph's
  `alt` text.

---

### Dependencies

| Dependency | Version | Required |
|---|---|---|
| three.js | 0.180.0 | Yes, as an ES module (see Quick Start) |
| GSAP core | 3.15.0 (3.12+ works) | Yes: matchMedia branching, teardown, the load fade and the played strokes (intro and keyboard sweeps) |

No GSAP plugins. The sort itself, the trail and its decay run on the
renderer's own frame loop, not on GSAP; GSAP moves a virtual pointer for the
played strokes, which the loop reads exactly like a real one.

Everything the effect uses (`WebGLRenderer`, `WebGLRenderTarget`,
`ShaderMaterial`, `PlaneGeometry`, `OrthographicCamera`, `Texture`) is
long-stable three.js API, so pinning to a different version is a one-line
change in the import map.

---

### Browser Support

Anything with WebGL2, which is every current browser. Support is **probed**
before three.js is asked for a renderer, deliberately: three logs its own
failure to the console, as errors, several times over, before it throws, so
catching the exception would hide nothing and a visitor with WebGL disabled
would get a console full of red on a page that had quietly fallen back.
Probed first, that visitor simply gets the photograph and a clean console.

Import maps are supported everywhere current. In a browser old enough to
lack them the module never runs, `window.THREE` is never set, and the
photograph is again what shows.

### Performance

Two draw calls a frame: the trail update at a quarter of the resolution, and
the image plane. At rest the plane is a single texture read per pixel, and
once the image has settled back to clean the loop stops drawing entirely
until the next input. The expensive part is the sort, which only runs inside
the band: each sorted pixel reads a fixed window of 21 samples along its row
and ranks them. The loop is gated on an `IntersectionObserver` and does not
run at all while the effect is off screen.

To buy back frames on low-end hardware, lower `data-max-dpr` first (to
`1.5`, or `1`): the sort costs per device pixel, so that is the largest
saving. After that, a smaller `data-band` and `data-spread` shrink the area
being sorted.

## What You Get

- `index.html`: working demo page
- `assets/script.js`: commented, readable source
- `assets/style.css`: effect styles
- `README.md`: full documentation with examples and framework integration notes
- `START-HERE-AI.md`: product-specific copy-paste prompt for AI-assisted setup
- `LICENSE.txt`: standard license terms
- Lifetime updates: re-download anytime from your library

## Get the Code

This is a premium effect. The standard license costs £10 one-time and covers unlimited personal and commercial projects with no attribution required for our code. Bundled third-party assets retain their own licences and attribution requirements. The only restrictions: no redistribution of the code itself and no competing effect libraries.

- [Buy Pixel Sort Drag](https://gsapvault.com/effects/pixel-sort-drag)
- [The Vault (£99 one-time, best value): every collection in the Vault library, plus future items added to those collections](https://gsapvault.com/effects)

## Judge the Code Quality First

These related effects are free with complete source published, written to the same production standard (cleanup functions, reduced-motion support, framework-agnostic):

- [Image Clip Reveal](https://gsapvault.com/effects/image-clip-reveal.md): A cinematic image reveal where a directional polygon aperture opens as the photograph settles from a restrained Ken Burns scale and its caption lands.
- [Spotlight Background](https://gsapvault.com/effects/spotlight-background.md): A theatrical GSAP and WebGL spotlight background: two stage follow-spots in a hazed room sweep in from the wings, cross over your headline and land on it, then trail the cursor like spots worked by an operator.

---

From [GSAP Vault](https://gsapvault.com): production-ready GSAP animation effects. Full catalog for agents: https://gsapvault.com/llms-full.txt
