Repository navigation
fix(core): keep overflowing content and SVG clip paths in motion-blurred frames - #7080
Conversation
…red frames A blurred mover was copied only within its own layout box, so text wider than the box (a one-line wordmark in a fixed-width container) was cut in blurred frames. The movers-alone capture also hid every non-mover, including the SVG clipPath a mover references, which empties the clip and makes the mover vanish. Both showed as flicker during playback and in full renders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the mover A block child is as wide as its mover however long its no-wrap text, so reading child boxes missed the common overflow layout. Read the text's own rects through a Range. On a rotated mover each child's upright client rect covers the mover's corners, which doubled the box at 45 degrees and could pull a neighbouring mover into the trail; only rects that leave the mover's own client rect now grow it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The upright client rect of a rotated mover has empty corners, so overflow that landed there passed the "stays inside" check and was still cropped. Measure a rotated mover with its own transform off and map the rects back through its layout pose, then restore its style. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With only transform: none, a mover with no position stops being the containing block of its absolute and fixed children, so they jump to an outer ancestor while measured and their real overflow is cropped. Add will-change: transform during the swap, and skip the swap when the mover has no transform of its own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Edit accuracy: accurate 267 (base branch 267), smooth 119 of thoseThe gate passes. Quarantined, measured but not gated (8)
Unstable (1)
|
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · f755566
I reproduced the containing-block case. The swap now adds will-change: transform, so an absolute child still resolves against the mover while it is measured. A mover that is turned only by an ancestor skips the swap and is measured with the upright box mapped back, which covers more than it needs to but never cuts anything off. Both new tests fail on 88b2de1 and pass here. All 197 motion-blur tests pass locally, and every workflow is green at this head.
Scope
I read all of frames.ts, filterPlan.ts and index.ts (the SVG-resource isolation rules), and the filterTier.ts/composite.ts wiring. rectsAtStyle has the same pre-existing exposure; the author has explicitly left it out of scope, and it is fine as a follow-up.
— Genesis
…view Adopts heygen-com#7294 (line_unread findings carry the measured frames), heygen-com#7271 (blur mutes discrete callbacks instead of undoing them), heygen-com#7293 (the filter tier's svg keeps the last child), heygen-com#7292 (a mover holding a fixed child stays sharp), heygen-com#7288 (auto motion blur draws with the filter tier) and heygen-com#7298 (Shift no longer re-renders the timeline). The fork's seek recovery in captureMotionBlur stays. Defers heygen-com#7134 (no packages/share) and heygen-com#7295 (needs the heygen-com#7080 crop pipeline). Watermark advances to 66cbabc. Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> Co-authored-by: Vance Ingalls <vance@heygen.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
Two fixes to page motion blur (
captureMotionBlur), both of which made elements flicker during playback and in full renders:defs,clipPath,mask,pattern,marker,symbol) stay visible while the moving elements are captured alone.Why
With
data-motion-blur="on", an endcard alternated between the full "HyperFrames Studio" lockup and a cut "HyperFra" with no logo. The composition's state was correct at every frame; only the blurred frames were drawn wrong.readMoverFramessized each copy asoffsetWidth/offsetHeightplus the glow pad.#brandis 540px wide but its one-line wordmark is 1528px, so blurred frames kept only "HyperFra". Sharp frames were fine, hence the alternation. The filter fallback crops to the same box, so it is affected too.<clipPath>a logo uses throughclip-path: url(#...). A hidden clip path clips everything, so the logo captured as nothing.On v0.9.7, a full render of that endcard had the wordmark cut in 43 of 91 frames and the logo missing in 31 of 44. With
--page-motion-blur=offboth were 0.Related work
Refs #7060 (a different motion-blur fix, for WebGL canvases).
How
frames.ts:contentBox()grows the mover's box to every descendant's client rect, mapped back into the mover's own px through the inverse of its pose. Elements that clip on both axes are left alone, and at most 256 descendants are read per mover. The frame now carries anorigin, so the box can start before the element's own origin.filterPlan.ts: the filter region is offset by that origin.index.ts: the hide rule excludes SVG resources and their contents, and a second rule sets them visible, so they do not inherithiddenfrom their<svg>.Test plan
frames.test.ts: content wider than its box is included (fails on v0.9.7:expected 540 ≥ 1528); content that stays inside keeps the box.index.test.ts: the isolation rules leave clip paths and their shapes unhidden (fails on v0.9.7).🤖 Generated with Claude Code