Motion blur
The final blends up to 32 subframes into each frame for motion blur. Add your own streak to a push, a whip or a zoom.
The renderer adds motion blurMotion blur is the streak of a fast move, made in the final by blending up to 32 subframes for each frame. to the final without your help. This page gives what that blur costs, and how to add a streak of your own to a fast move.
What you get without any work
A browser has no shutter, so CSS cannot blur a fast move in a video. The renderer does it by capture. For each output frame it seeks the page at several times inside the frame and takes a screenshot at each time. Then it blends the screenshots into one frame.
- A draftA draft is a fast render at half size and 30 fps, with sound, made by vawe dev. has no blur. A draft is 30 fps, half size.
- A finalA final is the full-quality render: 60 fps, motion blur and the mixed audio, followed by a final check. has up to 32 subframes for each frame.
bin/vawe shiprenders the final. - The shutter is 180 degrees. The blur spans half of the frame interval (
SHUTTER = 0.5inharness/media/render-page.mjs). - A still frame gets one subframe. A fast frame gets enough subframes that neighbours sit at most 3 px apart.
The blur has no flag on ship. To judge the blur, look at a final.
How the renderer counts
The function subframesForTravel(px, cap) starts at one subframe. It doubles the count while the swept distance for each subframe (travel * 0.5 / k) is above 3 px, up to the cap. The counts are powers of two. In a final the cap is 32.
| travel in one frame | subframes | gap between neighbours |
|---|---|---|
| 4 px | 1 | 2.0 px |
| 12 px | 2 | 3.0 px |
| 48 px | 8 | 3.0 px |
| 200 px | 32 | 3.1 px |
The travel comes from the page, before any screenshot.
- A page with CSS or Web Animations. A pre-pass reads the bounding boxes of the paused animations at consecutive frame times. It takes the largest move or resize of any animated element.
- A page driven by
window.seekorvawe.onFrame. The pass finds no boxes to read. It falls back to the mean pixel difference between small frames. A still frame gets 1, a medium frame gets up to 4, and a fast frame gets up to 16. The fallback cannot tell a 16 frame from a 32 frame, so a canvas or three.js page tops out at 16.
Add a streak of your own
The final blurs by time, and a draft has no blur. For a directional streak that also shows in a draft, use one of these four moves:
| move | how it makes the streak |
|---|---|
push-blur | An SVG feGaussianBlur on a panel. vawe.onFrame sets stdDeviation to half of one frame’s travel, on x only. |
whip-pan | The same filter on a strip of two shots. The blur is 0.4 of the travel per frame. |
crash-zoom | 16 copies of the frame at scales fanned across half of one frame’s change, stacked at opacities 1, 1/2, 1/3 and so on. The streaks point at the detail. |
smear-stretch | Shot A stretches into streaks on an ease-in, and shot B snaps back from the streak. |
The idea in push-blur is the pattern to copy. An SVG filter cannot be keyframed, so the hook sets it for each frame. It takes the slope of the same curve that moves the panel.
<svg width="0" height="0" style="position: absolute"><filter id="motion" x="-30%" width="160%"><feGaussianBlur id="mb" stdDeviation="0 0"/></filter></svg>
<div class="panel" style="filter: url(#motion)">in</div>
<script type="module">
import '../../core/engine/page-api.js';
import { EASE, easeFn } from '../../core/motion/presets.js';
const land = easeFn('land');
const panel = document.querySelector('.panel'), blur = document.getElementById('mb');
const t0 = 0.1, dur = 0.7, travel = 0.6 * window.innerWidth;
panel.animate([{ translate: `${travel}px 0` }, { translate: '0 0' }],
{ duration: dur * 1000, delay: t0 * 1000, easing: EASE.land, fill: 'both' });
vawe.onFrame((t) => {
const u = Math.min(1, Math.max(0, (t - t0) / dur)), du = 1 / 240;
const slope = (land(Math.min(1, u + du)) - land(u)) / du;
const pxPerFrame = (slope / dur) * travel / 60;
blur.setAttribute('stdDeviation', `${(u > 0 && u < 1 ? pxPerFrame * 0.5 : 0).toFixed(2)} 0`);
});
</script>A still panel gets a blur of 0. A moving panel blurs along its motion, never across it. The filter region is wider than the panel (x="-30%" width="160%"), so the blur is not cut at the edges. For a vertical push, swap the two stdDeviation numbers and the translate axis.
When to blur
The rule blur-follows-motion says that a still thing never blurs.
- Blur fast moves of shapes, panels and whole shots.
- Do not blur a slow drift, or small body text that holds still in the middle of a move. Blur makes it hard to read.
- For a word that moves fast, echo the outline of the glyph in a second colour under the type instead of blurring it.
- A blur-resolve entrance runs from about 24 px to 0 over about 0.4 s.
- A blur is not a lens effect. For depth blur, see Lens and effects.
The cost
Each subframe is a seek, a settle (fonts, image decode, two paints) and a screenshot. A final with more than 8,000 subframes prints advice with the count and an estimate of 1,000 subframes a minute. One measurement stands behind that estimate: 19,200 subframes took 15 to 23 minutes. For one 5 s three.js film, a cap of 16 made 4,017 subframes and a cap of 32 made 7,805.
To see the cost, run ship with --profile. It prints frames, subframes, screenshots, capture and encode seconds, and the five costliest seconds.
bin/vawe ship films/<name>/page.html --profileThe owner approves the draft before you ship. See Ship.
To cut the cost, shorten the fastest moves, or make fewer frames move fast at the same time. Two subframes with the same animation state reuse one screenshot.
Troubleshooting
| what you see | why | what to do |
|---|---|---|
| The draft has no blur. | A draft never has motion blur. | Look at the final, or add a streak of your own. |
| A fast object shows separate copies in the final. | Too few subframes for the travel. A canvas or three.js page is capped at 16. | Slow the move, or move less in one frame. |
| Small text turns to a smear mid-move. | The text moves fast and gets blurred. | Hold the text still while the shot moves, or echo its outline. |
| The final takes very long. | Many frames need 16 or 32 subframes. | Read the --profile table and shorten the fast seconds. |
Transitions
Moves
Ship
Lens and effects
From the blogCSS motion blur for video: blend 1 to 32 subframes