Displacement
Shear the frame into a grid of offset colour-fringed tiles under film grain and let them settle back on the next scene
This transition post-processes the real pixels of both scenes through the experimental html-in-canvas browser API. The preview above shows the true effect in Chrome 149+ with chrome://flags/#canvas-draw-element enabled (nested canvases need 152.0.7944.0+). Every other browser — Safari, Firefox, Chrome without the flag — falls back to a CSS approximation of the same cut, so the page and your video still play.
Rendering is supported through the CLI, Studio, Lambda and SSR with --gl=angle, or --gl=swangle on a machine without a GPU. Chrome may change or discontinue the API; the CSS fallback is what keeps your composition renderable if that happens.
Installation
$ pnpm dlx shadcn@latest add @remocn/displacementUsage
displacement() is a Remotion transition presentation. Pass it to a
TransitionSeries.Transition between two sequences. The frame divides into a
grid of cells and every one of them shears out of place at once, each on its own
beat: the picture inside a cell slides away from where it belongs, its colour
channels separate along the direction of travel, and film grain settles over the
distortion. At the peak the cells hand over, then slide back into register on the
next scene.
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { displacement } from "@/components/remocn/displacement";
export const MyVideo = () => (
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={45}>
<SceneA />
</TransitionSeries.Sequence>
<TransitionSeries.Transition
timing={linearTiming({ durationInFrames: 18 })}
presentation={displacement()}
/>
<TransitionSeries.Sequence durationInFrames={45}>
<SceneB />
</TransitionSeries.Sequence>
</TransitionSeries>
);Why this one needs the canvas
Each cell samples its scene at a coordinate pushed away from the pixel being written, by its own seeded direction and distance — so a cell shows a piece of the picture that belongs somewhere else, and the misalignment between neighbouring cells is the whole effect. On top of that the red and blue channels are sampled at two further offsets along the direction of travel, so a displaced tile fringes exactly the way a channel that has slipped does.
A mask cannot move image data. Nothing here is a fade, a tint, or a shaped reveal.
The whole frame at once
There is no band travelling across the picture. Every cell displaces
simultaneously, spread only by stagger, and every cell recovers. That is
deliberate: a boundary sweeping over one scene to reveal another is a wipe, and
a wipe does not need pixels — it would make the html-in-canvas dependency
decorative.
The entrance mirrors the exit
The same cell carries both scenes. It shears out holding the outgoing frame and returns holding the incoming one, and the hand-over sits at the peak of the shear, where the frame is furthest out of register and least readable. Played backwards the transition is the same displacement carrying the other scene.
Both ends are exact: the shear follows a half-sine over each cell's own phase, so it is zero at both ends and every cell sits in register on an undistorted frame.
Determinism
Each cell's direction, distance, phase and grain are seeded from its grid coordinate and evaluated as closed-form functions of the transition's progress — the grain advances on a clock derived from progress rather than from wall time, so there is nothing carried between frames. The same frame renders identically in every chunk of a parallel render.
Fallback
Where html-in-canvas is unavailable the two scenes cross with a short lateral jolt in opposite directions. It keeps the knocked-out-of-register feeling and the timing; the per-cell shear needs the scenes' pixels, so none of the props apply there.
Props
displacement(props) accepts:
| Prop | Type | Default | Description |
|---|---|---|---|
grid | number | 60 | Cells spanning the width of the frame. The default is a fine mesh; drop it toward 10 to shear the picture into large slabs instead. |
cellAspect | number | 1 | Width-to-height ratio of one cell. Values above 1 give wide, letterbox cells that read as scanline tearing; below 1 gives tall columns. |
shift | number | 1 | How far a cell travels at the peak of the shear, measured in cells — 1 is roughly its own width, so changing `grid` does not change how violent the effect looks. 0 leaves the grid in register so only the grain remains. |
aberration | number | 1 | Colour separation on a displaced cell, measured along its direction of travel. 0 keeps the tiles clean. |
grain | number | 0.5 | Opacity of the film grain over displaced cells. It rises and falls with the shear, so a settled frame is always clean. |
stagger | number | 0.45 | How far apart cells start their shear, as a share of the transition. 0 displaces the whole grid as one plane; high values leave a long tail of cells still out of register. |