Deferred from #3070 (thread), where the fix was judged to want a benchmark rather than eyeballing.
compositionPaintsAt is an uncached O(all stamped elements) walk, and the documented host recipe re-runs it on pointer movement and on every frame (animated artwork moves under a stationary cursor). Per call, per contained candidate:
querySelectorAll + getBoundingClientRect on every stamped element — ensureHfIds stamps every body descendant, so addressableOnly barely shrinks it
- up to three
getComputedStyle resolutions (isRenderedVisible, isOpacityVisible's inclusive re-read, styleInk)
- an eager full ancestor opacity walk plus a second
depthOf ancestor walk
- a fresh root query per painted hit when
fullBleedFraction > 0
On a ~1000-element composition that is order 100k rect/style reads per second, forcing layout flushes interleaved with GSAP's writes — janking the animation the overlay sits on.
Semantics-preserving wins identified in review:
- read computed style once per candidate and thread it through
- defer the visibility check into the lazy
find predicate beside the ink test
- single-pass min-tracking instead of a full sort
- cache frame area per document
Why this needs a benchmark, not a patch. Two of those interact with the lazy-sampling property pinned by samples alpha lazily — the smallest painting box wins before the rest are read: deferring visibility changes how many candidates get style-resolved, and replacing the sort with min-tracking changes the order ink is evaluated in. Both are safe on paper and both could quietly cost more alpha samples than they save. Measure on a ~1000-element composition before and after.
The host-side half already landed in #3070: the guide now says to coalesce pointer / Alt / playhead into one gated rAF query and to short-circuit before the walk when the pointer is outside the composition's box.
Deferred from #3070 (thread), where the fix was judged to want a benchmark rather than eyeballing.
compositionPaintsAtis an uncached O(all stamped elements) walk, and the documented host recipe re-runs it on pointer movement and on every frame (animated artwork moves under a stationary cursor). Per call, per contained candidate:querySelectorAll+getBoundingClientRecton every stamped element —ensureHfIdsstamps every body descendant, soaddressableOnlybarely shrinks itgetComputedStyleresolutions (isRenderedVisible,isOpacityVisible's inclusive re-read,styleInk)depthOfancestor walkfullBleedFraction > 0On a ~1000-element composition that is order 100k rect/style reads per second, forcing layout flushes interleaved with GSAP's writes — janking the animation the overlay sits on.
Semantics-preserving wins identified in review:
findpredicate beside the ink testWhy this needs a benchmark, not a patch. Two of those interact with the lazy-sampling property pinned by
samples alpha lazily — the smallest painting box wins before the rest are read: deferring visibility changes how many candidates get style-resolved, and replacing the sort with min-tracking changes the order ink is evaluated in. Both are safe on paper and both could quietly cost more alpha samples than they save. Measure on a ~1000-element composition before and after.The host-side half already landed in #3070: the guide now says to coalesce pointer / Alt / playhead into one gated rAF query and to short-circuit before the walk when the pointer is outside the composition's box.