data-motion-blur="on" to the composition root.
Turn it on
hyperframes render.
In the browser editor
The Blur tab in the browser editor’s right dock sets it with one control: Off, Subtle or Strong. Subtle is the default blur (240° shutter, samples sized to the smear). Strong opens the shutter to 720° and the samples to 64. The panel writes the attributes above to the composition root, so a render uses the same setting, and Undo works. Subtle and Strong also turn ondata-motion-blur-events, so motion set from a render(t) callback is found. Off removes data-motion-blur and data-motion-blur-events and leaves the other attributes where they are.
What it blurs
Elements that move quickly in 2D: position, scale and rotation changes from GSAP, CSS animations and transitions, and the Web Animations API. Slow movers and static content are left sharp. A mover’s glow and shadow travel with it, and the background behind it stays where it is.Two ways it is drawn
Filter puts an SVG filter on each mover: copies of the element along its path, averaged, and softened along the motion. It is whatdata-motion-blur-tier="auto" (the default) draws with. It works in any Chrome and over a WebGL canvas. On a four-whip test it cost about what a render without blur costs; on two real films it cost more than capture (see Cost and limits). Turns and zooms are only softened, not streaked.
Capture is opt-in with data-motion-blur-tier="capture". It copies each mover out of the page with Chrome’s HTML in Canvas API and redraws it along its path. It draws turns and zooms truthfully: a spinning tile smears along its arc and its centre stays sharp, and a zoom streaks outward. It has limits of its own, listed here (the filter tier’s are listed under Cost and limits, and a few, such as backdrop-filter, apply to both), so ask for it only for a film whose spins and zooms need the streak:
- Anything in front of a mover is lost: a static panel, an overlay or a title card over a moving element is drawn behind its smear.
mix-blend-modeandbackdrop-filteron a mover or its ancestor are not applied.- A CSS selector that reaches the root through its parent, such as
body > #root > .m, stops matching once capture wraps the root. - A mover sliding into an
overflow: hiddenor clipped card drags the clip edge along, two overlapping movers carry each other’s pixels, and content past the frame edge leaves a thin band. - In preview, the first frame after every play shows black for a moment.
chrome://flags/#html-in-canvas.
A page that asks for capture gets the filter tier instead when capture cannot run:
This is decided once for the whole composition, from the first frame. A defect the scan misses (a WebGL canvas added late, say) is caught per frame instead: that one frame is rendered sharp and counted in
failedFrames.
Blur is off, and stays off, for HDR renders and renders that use the engine’s own sub-frame blur. Distributed renders (plan, renderChunk, assemble, as the AWS Lambda and Google Cloud Run adapters run them) and --start/--end range renders blur like a whole local render. Pass --page-motion-blur off (or pageMotionBlur: "off" in a distributed config) to render them sharp.
Find out which one is drawing
In the page,
window.__hfMotionBlur.report() returns state, tier, reason, reasonCode and movers. A render logs one line per session with the tier, and a session-end line when any frames fell back to sharp (failedFrames > 0).
Cost and limits
- Render time. It varies by film. On an 8 s test composition with four whips, a render took 9.8 s with capture, 5.2–5.4 s with the filter tier and 5.3–5.9 s with no blur (two runs each, including startup). On two real 1080p films the filter tier cost more than capture, measured as CPU seconds of the whole render (wall time was too noisy on a loaded machine):
spacex-launch(77 movers, 39.5 s) took 547–552 CPU s with the filter tier, 302–320 with capture and 184–193 with no blur (three runs each, 2.9 times no blur);k3-promo(106 movers, 16.5 s) took 236, 205 and 117 (one run per arm, 2.0 times no blur). The two films do not order by mover count, and the cause of the difference was not isolated. The filter tier draws each mover as at least 32 offset copies per frame; capture adds a second paint per blurred frame. - Distributed renders. Blur applies in every chunk, drawn with the filter tier on
auto. Measured on two real 1080p films with plan,renderChunkand assemble at 240 frames per chunk, in a software-GPU Chrome 153 on a machine other work was loading (load average 12 to 25), one run per arm: summed chunk CPU seconds went from 155 to 246 (1.6 times) onk3-promoand from 338 to 545 (1.6 times) onspacex-launch; summed chunk wall time rose 1.2 and 1.4 times. A film withdata-motion-blur="on"also plans screenshot capture instead of BeginFrame capture, and the measured cost includes that switch. - Editing. The filter tier never wraps anything. With capture the root is wrapped only while playing or rendering; paused, it is plain DOM, so click-to-select works.
- Capture only. Blurred preview can run at half the frame rate on heavy compositions, and a
<video>drawn through the capture looks about 2–3% darker in the browser editor preview than in normal playback. Renders are not affected. - Preview and render.
autodraws the same way in both at full preview quality. When a host lowers preview quality (Studio’sset-preview-qualitymessage), the filter tier’s preview draws 16 copies at 1/2 and 8 at 1/4 and below; renders always draw 32. Against a sub-frame reference, 16 copies stayed within measurement noise of 32. 8 copies differ visibly on turning content. A page that asks for capture previews with the filter tier in a browser without HTML in Canvas, so a spin or zoom looks softer there than in the render. - Worker count. Output is not bit-identical between worker counts (around 60 dB PSNR), about the size of the difference chunking already causes.
- Not handled. Pose errors under CSS
zoom,position: fixedandforeignObject; a very short Web Animations API animation that falls between scan samples; a large see-through mover (a full-frame translucent sweep), which is left sharp while its fast children still blur. With capture, a mover is drawn above the static content, so a static element that should cover a mover does not. - Limits of the filter tier (the default), as measured on one-pattern test compositions:
::beforeor::aftercontent outside the mover’s box is cut off.- A mover’s own
clip-pathor mask gets hard edges: the smear is clipped to the shape it has at that frame. - A
position: fixedchild of a mover that moves vialeftjumps, because the filter makes the mover its containing block. - A
:last-childselector on the root’s children also matches the<svg>the tier adds to the root (nth-last-*works the same way but was not measured). - A visibility toggle inside the shutter window is drawn at full strength instead of half.
backdrop-filteron a mover differs from the 32-sample oracle in the sweep (the backdrop only).