Skip to content

perf: batch elements into shared IntersectionObserver instances per threshold - #17

Closed
andreas-fahrecker wants to merge 1 commit into
heidkaemper:mainfrom
andreas-fahrecker:reduce-observer-amount
Closed

andreas-fahrecker wants to merge 1 commit into
heidkaemper:mainfrom
andreas-fahrecker:reduce-observer-amount

Conversation

@andreas-fahrecker

Copy link
Copy Markdown
Contributor

Summary

Groups observed elements by their resolved threshold value and shares
one IntersectionObserver per threshold, instead of creating a new
IntersectionObserver for every observed element.

Motivation

observe() currently creates one IntersectionObserver per element.
On a page with many intersect: elements sharing the same threshold
(the common case, since most elements just use the default threshold
or intersect-half/intersect-full), this means far more observer
instances than necessary. Since restart() disconnects and rebuilds
all observers from scratch, this cost is also repeated on every call
to it (e.g. on SPA route changes).

Changes

  • observe() now groups elements by their resolved threshold value
    first, then creates a single IntersectionObserver per distinct
    threshold, observing all elements that share it.
  • The intersection callback now reads the target element from
    entry.target instead of closing over the per-element element
    variable, since a single callback now handles multiple elements.
  • intersect-once elements are removed from their observer with
    observer.unobserve(el) instead of observer.disconnect(), since
    disconnecting would now stop observing every other element sharing
    that observer.

Backwards compatibility

No API changes. Behavior of intersect-once, intersect-half,
intersect-full, and restart() is unchanged from the outside —
this is an internal implementation change only.

Testing

Tested locally in an Astro project (not the repo's demo page), with
both this change and the arbitrary-threshold PR applied together:

  • intersect-once still stops observing the correct element once
    triggered, without affecting other elements sharing the same
    observer/threshold.
  • Elements without intersect-once continue to toggle in and out as
    expected on repeated scroll in/out.
  • Existing intersect-half and intersect-full still resolve and
    trigger correctly.
  • Arbitrary intersect-[value] thresholds (from the other PR) also
    still trigger correctly when combined with this change.

@heidkaemper heidkaemper self-assigned this Sep 6, 2026
@heidkaemper

Copy link
Copy Markdown
Owner

Thank your for the contribution!
I'm going to close this one for the following reasons:

  1. The complexity isn't worth it. IntersectionObserver instances are quite cheap. And grouping elements into a Map adds complexity to what is currently a straightforward loop. I'm trying to keep this plugin as small and readable as possible.

  2. There seems to be a bug in the element lookup. Using document.querySelector (singular) instead of querySelectorAll caused some problems.

If you ever run into a measurable performance problem here, I'd be happy to look at it again. Thanks again for taking the time.

@heidkaemper heidkaemper closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants