Pixel Stretcher
Stretches an image or video into pixel-trail streaks driven by scroll, hover, or both. Nineteen props cover source handling, streak strength, trigger mode, backing colour and sizing.
Included with membership
Members install Pixel Stretcher with one command and get the full source, the usage code and the Framer remix, licensed for client work. One membership covers the whole library and the CLI.
When to use it
An image that should bleed into the space around it rather than stop at its own edge — a hero on a plain background, a card that reaches into the margin. It needs empty space next to it to bleed into; inside a tight grid there is nowhere for the stretch to go and the effect is invisible.
- Leave whitespace on the sides you list in bleedSides — that is the room the effect uses.
- Use trigger="scroll" for something below the fold and "hover" for something the visitor is meant to touch; "both" is for a hero that should react either way.
- Give the element a size through width/height, aspectRatio, or className.
- Set backgroundColor to match what is behind it, so the bleed fades into the page rather than onto a seam.
Avoid:
- Do not bleed into a side that touches another element. The stretch draws over the top of it.
- Do not put many of these on one page. They share a single full-screen overlay canvas, and every instance draws into it every frame.
- Do not raise strength to compensate for a cramped layout — the fix is space, not amplitude.
Props
An image or video whose edge pixels smear out past its frame as you scroll, so the picture bleeds into the page instead of stopping at a hard border. Can be driven by scroll, by hover, or both.
<PixelStretcher />
The media and its bleed.
| Prop | Type | Default | Description |
|---|---|---|---|
src* | string | — | Image or video to show. Required. |
isVideo | boolean | false | Treats the source as a video. Set it explicitly — the source is not sniffed. |
alt | string | "" | Alternative text. Set it unless the media is decorative. |
trigger | "scroll" | "hover" | "both" | "scroll" | What drives the stretch. |
strength | number | 1.0 | How far the pixels smear. 0 leaves the media in its frame. |
bleedSides | ("top" | "bottom" | "left" | "right")[] | ["bottom"] | Which edges the picture bleeds from. Only the bottom by default — pass more for a fuller spill. |
overlap | number | 1.5 | How far past the frame the bleed reaches, as a multiple of the frame. Higher spills further into the page. |
sampleOffset | number | 4 | How far in from the edge the smeared pixels are read, in pixels. Raise it if a dark border is bleeding instead of the picture. |
elasticStrength | number | 1.0 | How much the smear springs and overshoots rather than moving evenly. |
lerpSpeed | number | 0.075 | How quickly the stretch follows its target, per frame. Lower trails further behind. |
fadeSpeed | number | 0.075 | How quickly the bleed fades out as it recedes. |
maxOpacity | number | 1.0 | Strongest the bleed gets. Lower it to keep the spill subtle. |
holdTime | number | 3000 | Milliseconds the bleed stays out after the trigger stops, before it recedes. |
backgroundColor | string | "transparent" | Colour behind the media. Transparent by default, so the bleed reads against your page. |
width | string | number | "100%" | Width of the frame. |
height | string | number | "auto" | Height of the frame. Left auto, aspectRatio decides it. |
aspectRatio | string | "aspect-[4/3]" | Shape of the frame, given as a utility class rather than a CSS ratio. Note the default is a class name, not a number. |
crossOrigin | "anonymous" | "use-credentials" | — | Passed through to the underlying image or video. The bleed is drawn rather than sampled, so media on another domain works without this — set it only if something else in your page needs the media fetched with CORS. |
className | string | "" | Applied to the wrapper. |
dpr | number | the screen's | Pixel ratio the bleed is drawn at, before the quality tier scales it. Every instance on the page shares one canvas, so the highest value anyone asks for is the one used. |
disableAdaptiveQuality | boolean | false | Draws at full resolution on every device. By default the canvas is scaled to 0.85 on a mid tier and 0.7 on the most constrained one. Because the canvas is shared, one instance setting this gives full quality to all of them. |
mount | "auto" | "eager" | "eager" | When to join the shared overlay. The default joins on mount. Set "auto" to wait until the element is within about 200px of the viewport, which is ample here: the stretch is driven by scroll speed and cannot fire before the element is on screen. What it saves is the overlay itself — the full-window surface and its listeners are created by the first instance to join and torn down by the last to leave, so on a page whose stretchers are all below the fold, none of it exists until one is close. Neither setting takes it off the shared frame loop or overrides the reduced-motion preference. A scene running its own loop would be invisible to the scheduler, so it could not be set aside under load, and it would slow every other animation on the page rather than only itself. |
crossOrigin and a permissive CORS header at the source. Without both the picture still shows, but the bleed silently does not.overlap times the frame. Large overlaps on a full-width image are the expensive case.