Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Svelte: optimize observeIntersection#63816

Merged
camdencheek merged 5 commits into
mainfrom
cc/optimize-observe-intersection
Jul 17, 2024
Merged

Svelte: optimize observeIntersection#63816
camdencheek merged 5 commits into
mainfrom
cc/optimize-observe-intersection

Conversation

@camdencheek

@camdencheek camdencheek commented Jul 14, 2024

Copy link
Copy Markdown
Member

While attempting to use observeIntersection with the new references panel, I was running into performance issues with large lists of references (the API does not actually paginate yet). When I took a look at the profile, a good chunk of the time came from finding the nearest scrolling ancestor, specifically the overflowY part, which requires computing large chunks of the layout.

So this changes the observeIntersection action to take an explicit target container so we don't need to do any searching for a scroll container. For the example that I was debugging, this reduced the time it took to render the list ~5x. Additionally, it establishes a cache for all created IntersectionObservers rather than just the root observer.

Before profile (5.5s):
screenshot-2024-07-13_22-42-58@2x

After profile (1.1s):
screenshot-2024-07-13_22-43-49@2x

Test plan

Tested that scrolling still triggers highlighting in the places this is used, and that the performance impact is much lower for large lists (screenshots above).

@cla-bot cla-bot Bot added the cla-signed label Jul 14, 2024
@camdencheek camdencheek force-pushed the cc/optimize-observe-intersection branch from bf47611 to 7f0a9f4 Compare July 14, 2024 05:19

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WeakMap is used to avoid holding onto a reference to elements that would otherwise be garbage collected.

@camdencheek camdencheek Jul 14, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all getting changed in the upcoming usages PR, so I didn't bother fixing it for this. It just means things get highlighted when intersecting with the root element rather than their scroll container (which actually doesn't matter in this case because they share a bottom boundary)

<StreamingProgress {state} progress={$stream.progress} on:submit={onResubmitQuery} />
</aside>
<div class="result-list" bind:this={resultContainer}>
<div class="result-list" bind:this={$resultContainer}>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL it works to just bind directly to a writable

@camdencheek camdencheek changed the title Svelte: optimize observeIntersection Svelte: optimize observeIntersection Jul 14, 2024
<div bind:this={root} use:observeIntersection on:intersecting={event => (visible = event.detail)} class="matches">
<div
bind:this={root}
use:observeIntersection={$scrollContainer}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I passed in the scroll container to maintain the same behavior here, but it might be cleaner to just not bother with the scroll container and fall back to the root element.

Comment on lines 55 to 62
export function capture(): SearchResultsCapture {
return resultContainer?.scrollTop ?? 0
return $resultContainer?.scrollTop ?? 0
}

export function restore(capture?: SearchResultsCapture): void {
if (resultContainer) {
resultContainer.scrollTop = capture ?? 0
if ($resultContainer) {
$resultContainer.scrollTop = capture ?? 0
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binding doesn't happen until the component is mounted, which means that when this script is first run, resultContainer is null, which means the correct typing is HTMLElement | null rather than HTMLElement. That makes this a little more awkward, but having a technically-incorrect type seemed worse.

@camdencheek camdencheek marked this pull request as ready for review July 15, 2024 16:24
@camdencheek camdencheek requested a review from a team July 15, 2024 16:24
@camdencheek camdencheek force-pushed the cc/optimize-observe-intersection branch from c65e8b2 to 5ac65b3 Compare July 17, 2024 00:08

@vovakulikov vovakulikov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember I had the same issue in tooltip tether logic, it was fine though since
it was just one element where I run this logic with finding scrollable block.

Another solution here would be to change our approach with visibility and switch to the real virtual scroll behavior. But probably this change it's too big at this stage, LGTM

Thanks

@camdencheek camdencheek merged commit 8e6bf0e into main Jul 17, 2024
@camdencheek camdencheek deleted the cc/optimize-observe-intersection branch July 17, 2024 02:13
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants