Replies: 3 comments
-
A potentially interesting new CSS feature from Chrome 85: https://web.dev/content-visibility/ And the relevant Firefox implementation bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1660384 |
Beta Was this translation helpful? Give feedback.
0 replies
-
Replacement in the works in #606 |
Beta Was this translation helpful? Give feedback.
0 replies
-
There's a custom solution implemented after Vue 3: VirtualGrid |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This is a thread to discuss between different DOM Recycling approaches for this client while browing the library. DOM recycling is a technique that recycles already on-browser elements, updating their data, giving the sense that the user is scrolling a much large webpage while, in realiting, content is being swapped between already existing elements. More info here: https://developers.google.com/web/updates/2016/07/infinite-scroller
There are two possible approaches to this:
Full DOM Recycling: The webpage is rendered and 100% looks like it's fully loaded. That means that the scrollbar is sized accordingly. This is the approach we actually use in master since Add virtual scroller to library view #119 was merged. Example here: https://akryum.github.io/vue-virtual-scroller/#/dynamic
Lazy loading: The content loads as soon as the scrollbar reaches the edge of the page. Example here: https://codepen.io/rsschouwenaar/pen/jzYGJM
At first, I thought that Full DOM recycling was the best way to go but, actually, I'm not sure. When there are thousands of elements, having a little scrollbar seems a bit ridiculous. However, I don't think we have another choice, as we need to remove the elements that are above the on-screen elements anyway. It's possible, but perhaps might make exploring libraries a nightmare.
Current virtual scroller has a notable performance hit (see #119). Changing from
DynamicScroller
toRecycleScroller
should improve things. However, I think we should make our own recycling, instead of using the vue-virtual-scroller library. Here are my reasons:It's for general purpose: The library is great for everyone who wants a quick thing that works with everything running up quickly. That "do-it-all" approach is great, but it comes with a performance hit and, in our heavy-work scenario, every milisecond matters and we want to squeeze CPU cycles as much as possible.
We have a really specific use case: We only need DOM recyling for one thing: library view. Nothing else. Our implementation might not work in the home screen or settings. Or in other applications. But we only need it in library browing: if it works for library browser, we're done. That's the only purpose for our recycle logic.
We have full control over it
With that said, I want to explain the approach I have in mind that (imo) could work:
Create a
VirtualLibrary
component that will be a wrapper to all the area where the library cards appear. Will have two required props:Inside this
VirtualLibrary
component, there will be theLazyCard
orVirtualCard
one with two props:This component will create the
Card
component we actually have using anv-for
and a classhide
, which will have adisplay: none
property (so DOM operations are really quick and don't force a reflow).During initialization, the
VirtualCard
component will remove the class from the components, so we force a browser repaint. It will check for theoffsetHeight
in the first one that its mounted and forgetBoundingClientRect
for the rest of them. As soon as the DOMRect array of an element changes it'stop
position, we know how many items per row exist. We set a bool flag and stop checking. We multiply the offsetHeight by the number or rows + some headroom and we have the height of the page, which we can pass back to theVirtualLibrary
component so it sets its height accordingly, forcing an scrollbar.At this point, what we need to do is to keep removing the class for the subsequent elements that must be rendered, based on the prop that was passed before
All the following elements, after mounted, get their DOM nodes pushed to an array.
With a previously created IntersectionObserver, we observe the elements that are on screen. When scrolling down, the callback function will get called. If items with array's indexes 100, 101, 102, 103 and 24 are on the top edge of the screen and we've set to keep 100 elements on screen, we know that we can remove item 0, 1, 2, 3 and 4 from the DOM. We can make a dictionary
{index: DOMNode}
or use adata-index
attribute for keeping a track of our off-screen nodes and the on-screen ones. We chunk the nodes to remove in another array and doremoveChild
in a single call, so it's fast. We can update those nodes in the original off-screen array to "refresh" them (but, previously, we loop over them so we set thehide
class again``)Same thing applies when items must appear: we loop over the indexes that must be added, removing the
hide
class, chunk them and do a singleappendChild
, which means only one repaint.We will need to hook an event listener that tracks when window is resized, so we can recalculate the scrollbar height again.
I'm not sure how this will work with Vue's reactivity (that's why I mention the refresh thing before). I guess it might still work, as we're pushing the original nodes from an off-screen array. However, it probably won't, I don't have an idea to be honest. Anyway, at this time, we didn't had the need for reactivity at all in the cards after they've been mounted.
There's a minor scale behaviour of all of this here. See how I observe and unobserve elements in an array based on the scroll speed.
With all of that said, I'll leave this to discussion time. Every kind of input in this matter is really important so please, voice any idea or concern you have about this. This is really important as how we make this approach might mark how future development will go.
Beta Was this translation helpful? Give feedback.
All reactions