haltu / muuri

Infinite responsive, sortable, filterable and draggable layouts
https://muuri.dev
MIT License
10.8k stars 643 forks source link

Animate only the items that are in the viewport #398

Open niklasramo opened 4 years ago

niklasramo commented 4 years ago

After benchmarking the latest v0.9.0 release I've noticed that when you start the animation for hundreds of items there is a noticeable dip in fps. As the animations run in another thread (thanks to Web Animations API) they do run smoothly as long as they are not manipulated via the DOM in any way, but the pain points are starting the animations and stopping the animations.

So I was thinking how to make this process faster and the logical thing that came to my mind was windowing, as in animating only the visible items (in the viewport) and just instantly snapping the items outside the viewport. As a matter of fact, this is how Muuri worked in the early days out of the box, but I dropped that optimization somewhere down the road.

The optimization would be applied to both visibility and layout animations. And also, if an item is moved into viewport with animation and starts off outside the viewport it would be animated also. I have a working version of this optimization in a private branch and it's working pretty nicely, laying the foundation for full virtualization support and providing a noticeable boost in perf with hundreds of items. I'm just wondering if the optimization should be optional or not πŸ€”

If you have any ideas/opinions on how to best fit this optimization into Muuri please do share your thoughts πŸ™

seanstrom commented 4 years ago

Not sure what the best way forward would be for this optimization, though I'm wondering if it could be made optional. Based on some work using react-virtualized, I've noticed that there are some cases where recycling the DOM nodes can make it more difficult to use web elements with embedded states (iframes). I've also come across some other techniques that involve marking items that are hidden from view as "invisible", and then allowing that to trigger an optimization for cheaper item renders.

In either case it seems nice to be able to pick and choose which set of optimizations you'd like to use, though the complexity of maintaining multiple combinations could be unideal. Perhaps if the optimization was not optional it would be much simpler to maintain, but maybe tradeoff some edge cases that don't play well with a DOM recycling or a visual occlusion based strategy. What approach does Muuri take?

niklasramo commented 4 years ago

Thanks for the ideas and feedback @seanstrom πŸ’‘ πŸ‘

In my private branch I did put the optimization behind a grid option and gladly it is not that much added complexity. Also, after playing around a bit with the optimization on/off I'm pretty sure it's not ideal to have it enabled all the time. If you happen to scroll the page while dragging it does look a bit nicer when all the items are animated. So I think the optimization will be released as optional and by default it's disabled.

Next up after that is adding full virtualization support, which is gonna be pretty interesting. I'm wondering if any current virtualization mechanism/library can handle an arbitrary layout πŸ€” The ones I've seen can handle lists and grids to a certain degree. The nice thing with Muuri is that we already have a mechanism to access the virtual (and rendered) position of every item, that mechanism is kinda the core of Muuri. So in theory, as the first step, all we need to do is just set item's display to hidden if it's not in viewport and show it if it enters viewport. If we want to go deeper and try to save some memory too we need to render/unrender the items based on if they are in the viewport or not, which is going to be a bit more tedious to implement..

seanstrom commented 4 years ago

Glad to hear that Muuri's structure is already compatible with virtualization! I'm also not aware of a library that supports the feature set of Muuri with virtualization.

My initial thought would be to render an empty block with the same dimensions as the real item and then switch back to the normal rendered version when in view. Would that approach be useful? What kind of roadblocks could we expect in general around rendering/unrendering elements?

niklasramo commented 4 years ago

render an empty block with the same dimensions as the real item and then switch back to the normal rendered version when in view

πŸ€” hmmm.. I believe we don't need to render an empty element... just render nothing outside viewport or alternatively set the display property of the elements to none so that they won't be part of the browser's rendering process.

What kind of roadblocks could we expect in general around rendering/unrendering elements?

Animations might be tricky to handle, possibly. Also if we go the extra mile and remove the elements from the DOM while they are outside viewport there might be need to do a light weight destroy process for those items to unbind some event listeners for the duration of invisibility and then rebind the listeners when the item becomes visible.