[audit] motion-dom/projection: reduce per-frame allocations#3645
Merged
mattgperry merged 2 commits intomainfrom Mar 16, 2026
Merged
[audit] motion-dom/projection: reduce per-frame allocations#3645mattgperry merged 2 commits intomainfrom
mattgperry merged 2 commits intomainfrom
Conversation
- Pre-compute border radius label strings to avoid template literal concat per frame - Replace scroll offset object allocations with direct translateAxis calls in applyTreeDeltas and applyTransform - Inline applyTransform at resolveTargetDelta to write directly into this.target, eliminating per-frame createBox() allocation (resolves TODO) - Reuse layoutCorrected box instead of creating a new one each updateLayout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR reduces per-frame memory allocations in the layout animation projection system — the hottest path during layout animations. Every projecting node runs
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["resolveTargetDelta (per frame)"] --> B{resumingFrom?}
B -->|Yes - OLD| C["applyTransform(layoutBox)"]
C --> D["createBox() ❌ allocation"]
D --> E["copyBoxInto(newBox, layoutBox)"]
E --> F["loop: transformBox({x,y}) ❌ allocation"]
F --> G["return newBox → this.target = newBox"]
B -->|Yes - NEW| H["copyBoxInto(this.target, layoutBox)"]
H --> I["loop: translateAxis ✅ zero alloc"]
I --> J["transformBox on this.target in-place"]
B -->|No| K["copyBoxInto(this.target, layoutBox)"]
L["applyTreeDeltas (per frame)"] --> M{scroll offset?}
M -->|OLD| N["transformBox(box, {x,y}) ❌ allocation"]
M -->|NEW| O["translateAxis(box.x/y) ✅ zero alloc"]
P["mixValues (per frame)"] --> Q{border radius?}
Q -->|OLD| R["template literal ❌ 4 strings/frame"]
Q -->|NEW| S["static array lookup ✅ zero alloc"]
Last reviewed commit: bbdb6ab |
Instead of inlining the applyTransform logic, add an output parameter so callers can pass an existing box to write into, avoiding per-frame allocation without code duplication. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this directory does
motion-dom/src/projection/implements the layout animation projection system. It handles per-frame box math during layout animations — measuring element positions, calculating deltas between layout states, applying tree transforms, and building CSS projection transforms. This is the hottest path in layout animations: every projecting node runsresolveTargetDelta,calcProjection, andapplyProjectionStylesevery frame.Findings
Audited all 25 source files for per-frame allocation and unnecessary computation:
Per-frame box allocation in
resolveTargetDelta— When an element resumes from another (shared layout transitions),this.applyTransform()was called every frame, creating a newBoxobject viacreateBox()each time. A TODO comment acknowledged this.Per-frame object allocations for scroll offsets — In
applyTreeDeltasandapplyTransform, scroll offset corrections created a new{ x, y }object each call, then passed it throughtransformBox→transformAxis→applyAxisDelta→scalePoint— all to perform a simple translation.translateAxisdoes the same work with zero allocations.Per-frame string concatenation in
mixValues— The border radius mixing loop built`border${borders[i]}Radius`template strings on every frame (4 strings × every frame during crossfade). Pre-computing the labels avoids this.Redundant
createBox()inupdateLayout—layoutCorrectedwas replaced with a fresh box on every layout update, butcalcProjectionimmediately overwrites it viacopyBoxInto. Lazy-initializing once is sufficient.Changes
node/create-projection-node.tsapplyTransformlogic atresolveTargetDeltato write directly intothis.targetcreateBox()allocation (resolves TODO)node/create-projection-node.tstransformBox(box, {x, y})withtranslateAxiscalls inapplyTransformgeometry/delta-apply.tstransformBox(box, {x, y})withtranslateAxisinapplyTreeDeltasgeometry/delta-apply.ts+=intranslateAxisanimation/mix-values.tsborderLabelsarray with full stringsnode/create-projection-node.tslayoutCorrectedinstead ofcreateBox()eachupdateLayoutTest plan
yarn buildpassesyarn testpasses (all 92 suites, 774 tests)🤖 Generated with Claude Code