Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manually mount slots #6360

Open
probablykasper opened this issue May 24, 2021 · 13 comments
Open

Manually mount slots #6360

probablykasper opened this issue May 24, 2021 · 13 comments

Comments

@probablykasper
Copy link

Is your feature request related to a problem? Please describe.
I have a virtual list component VirtualList where it takes a little time to load each item, so that this effect is quite prevalent when scrolling:

xScreen.Recording.2021-05-24.at.2.25.20.AM.mp4

I imagine the issue here is that whenever a new item becomes visible, all visible items are re-rendered.

Describe the solution you'd like
The fastest virtual list I've seen is VSCode's. It works by appending new elements to the view without being concerned about what order they're in, and positioning them using position: absolute:
Screen Shot 2021-05-24 at 2 41 01 AM

The best way I can see to accomplish this is to be able to manually mount Svelte slots from JS.

Another possible solution would be to have an #each block where you could add or remove items without re-rendering any items.

Describe alternatives you've considered

  1. Mounting the child components manually, instead of using slots. Should be possible, but I believe it would make it significantly more complicated to interact between the Container component and the Item components (keyboard events, drag-and-drop events, row selection), as well as sharing styles between them. Keeping slots would be great.
  2. I could make a big soup of a Svelte component that includes both the container and the virtual list logic
  3. Tried using <svelte:options immutable/>, but doesn't seem like something that would work here.

How important is this feature to you?
Quite important, but I understand it's an edge case.

@johnoscott
Copy link

How much faster is it implementing vitual scrolling with this technique ? If it is significantly faster then this might be worth pursuing IMO.

@probablykasper
Copy link
Author

@johnoscott Definitely a noticeable difference. I've found this effect in pretty much every virtual list I've seen, but never once in VSCode.

@Prinzhorn
Copy link
Contributor

Prinzhorn commented May 28, 2021

I imagine the issue here is that whenever a new item becomes visible, all visible items are re-rendered.

That depends entirely on the implementation. I've been able to create incredibly smooth virtual scrolling for fixed height items. It's definitely possible with Svelte. Could you share an absolute minimal REPL?

It works by appending new elements to the view without being concerned about what order they're in, and positioning them using position: absolute:

From the screenshot I don't think that description is accurate. The order very much matters. I think it never appends new elements. It intelligently re-uses items and put them back at the top/bottom when they leave the viewport. That's absolutely possible with Svelte and just a matter of intelligently shifting your array slice. If you look at the value of top it's sequential (increments of 18) but it simply wraps around.

@probablykasper
Copy link
Author

That depends entirely on the implementation. I've been able to create incredibly smooth virtual scrolling for fixed height items. It's definitely possible with Svelte. Could you share an absolute minimal REPL?

@Prinzhorn Haven't seen a single Svelte virtual list without this. Here are the first 3 I found on Google:

From the screenshot I don't think that description is accurate. The order very much matters. I think it never appends new elements. It intelligently re-uses items and put them back at the top/bottom when they leave the viewport. That's absolutely possible with Svelte and just a matter of intelligently shifting your array slice. If you look at the value of top it's sequential (increments of 18) but it simply wraps around.

Here's a screenrecording showing that it does add and remove elements when you scroll:

Screen.Recording.2021-05-28.at.3.22.10.PM.mp4

@Prinzhorn
Copy link
Contributor

Prinzhorn commented May 28, 2021

I'll take this back partially. It seems to depend on the browser and I don't know if manually mounting slots changes anything about that. Updating things while scrolling has always had slight delays on certain browsers. The only thing you can do is have enough leeway (additional items at the top and bottom outside the viewport) so that it is not noticeable any longer.

VSCode (or monaco rather) does not count, because it's entirely virtual (the scrollbar is not native, so they can control the rendering 100%). You can see that the parent of those items is moved as well to achieve the actual scrolling. It's also stepped scrolling, not smooth scrolling. So way less updates.

Here's a video of my implementation that re-uses items. In Firefox (video) there is slight delay of rendering new items. In Chromium it's super smooth.

vokoscreen-2021-05-28_19-42-29.mp4

@probablykasper
Copy link
Author

@Prinzhorn Ah, I see that VSCode doesn't use normal scrolling, good observation. Adding a buffer does indeed help quite a bit, but only partially (had tried that before opening this issue). For simple lists, using a buffer should be fine.

How did you go about implementing that? I've only tested this on Chromium, so if you say it's super smooth then that's promising for sure.

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Jun 25, 2021

@probablykasper I can't extract this from my code base rn, but the idea is to not key your each so that the elements are re-used. And then you need to shift your array slice over and translate the items that are now in the "wrong" place. But I think this still doesn't solve your original issue, because my implementation also lags slightly in Firefox. That's something native scrolling will always suffer from in one way or another (I've done my fair share of scrolling animations). If you need full control then you need virtual scrolling which comes with it's own issues and usually worse UX.

@probablykasper
Copy link
Author

@Prinzhorn Been trying to figure it out for a while, but really not sure how it would be done. The way I tried to do it is this:

{#each Array(visibleCount) as _, i}
  <slot
    item={getItem(visibleIndexes[i])}
    index={visibleIndexes[i]}
    pos={visibleIndexes[i] * itemHeight} />
{/each}

Then, I for example run visibleIndexes[i] += visibleCount when the user scrolls down. The elements themselves are reused, but it still reruns getItem() for every visible element. (Made a REPL, doesn't run for some reason but works fine locally)

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Jul 21, 2021

@probablykasper I extracted it from my code base

https://github.com/Prinzhorn/better-svelte-virtual-list
https://svelte.dev/repl/cbf7977674654d048c4a0fa8fcc1024b?version=3.38.3

doesn't run for some reason but works fine locally

REPL thinks it's an endless loop with 1 million rows

@probablykasper
Copy link
Author

probablykasper commented Jul 25, 2021

@Prinzhorn Thanks for sharing it. I tried it out, but that too seems to rerun getItem() for every visible row when you scroll (modified REPL with logs).

For now I'm caching the values in an array, that seems like the best that can be done currently

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Jul 26, 2021

Thanks for sharing it. I tried it out, but that too seems to rerun getItem() for every visible row when you scroll (modified REPL with logs).

I don't see a problem with that. It's just a property lookup in my case (item.title) and Svelte will make a string comparison and then not touch the DOM. We're talking nano seconds. In my application the title could actually change, so I want that comparison and caching will introduce bugs.

Edit: The alternative (with keyed each) is that you get constant appendChild/removeChild during scrolling, which is not acceptable. Look at it this way: Svelte's "surgical updates" are caching. The DOM is sort of the cache and Svelte will only update it if the values change.

@probablykasper
Copy link
Author

probablykasper commented Jul 26, 2021

@Prinzhorn The problem is the extra code/complexity of having to manually implement a cache

@probablykasper
Copy link
Author

Here's a REPL showing the issue:
https://svelte.dev/repl/8c5c02002863418792dcdf2911ea6ba9?version=3.51.0

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

No branches or pull requests

3 participants