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

Support just-in-time measured content #6

Open
bvaughn opened this issue May 30, 2018 · 137 comments
Open

Support just-in-time measured content #6

bvaughn opened this issue May 30, 2018 · 137 comments
Assignees

Comments

@bvaughn
Copy link
Owner

@bvaughn bvaughn commented May 30, 2018

In order to avoid adding cost to existing list and grid components, create a new variant (e.g. DynamicSizeList and DynamicSizeGrid). This variant should automatically measure its content during the commit phase.

MVP

The initial implementation of this could work similarly to how CellMeasurer works in react-virtualized:

  1. Content is measured only if no current measurements exists.
  2. Measurements need to be reset externally (imperatively) if something changes.
  3. Cells at a given index can only be positioned after all cells before that index have been measured.

Goal

This component could perform better if we removed the third constraint above, allowing random access (by either item index or scroll offset) without measuring the preceding items. This would make react-window much more performant for use cases like chat applications.

This would also unlock the ability to use a ResizeObserver (via react-measure) to automatically detect item sizing and remove the position and measurements cache entirely. This would remove the need for imperatively resetting cached measurements and dramatically improve the API.

In order for the above to be possible, the dynamic list/grid components would need to use a dramatically different approach for mapping offset to index and vice versa. (This comment about "scroll anchoring" in react-virtualized has some nice visuals.) Essentially, we would need to do something like this:

  • Estimate total size based on the number of items multiplied by a estimatedItemSize prop. (This estimated size won't need to be adjusted, since the mapping described below doesn't is fuzzy.)

  • When scroll position changes, compare the new offset to the previous offset. If the delta is greater than some [to be determined] threshold, set the new offset as the "scroll anchor". Map the offset to an estimated index (e.g. divide the offset by total estimated scrollable size and multiply that by the number of items in the collection). Store this mapped index as the "anchor index". For example, if the list described by the image below had 250 items, the "anchor index" would be 132.

screen shot 2018-06-10 at 11 58 38 am

  • When scroll position changes, if the delta is less than the threshold, choose which new items to render relative to the anchor index. Position these items relative to the previously positioned items. Continuing with the example above, if the list was scrolled by a small amount (200px) then 200px worth of additional rows would need to be appended below the previously positioned items:

screen shot 2018-06-10 at 12 01 01 pm

The above approach has only one major downside: aligning items correctly at list boundaries. If item indices are estimated (as described above) then they likely won't line up exactly with the beginning or end of the scrollable area.

  • The end could potentially be accounted for by adjusting the total estimated size as the user scrolls closer to the end (although this might make scrolling feel janky).

  • The start of the list is harder to handle, since the first item needs to align with offset zero while still appearing to connect contiguously with items from some offset greater than zero. Perhaps another threshold could be used, a "safe zone", near the start of the list (e.g. if the scroll offset is less than some absolute value) which would force the list to measure all cells up to that point so they align correctly. The cost of this forced measurement would be relatively low, since it would only be a small number of items.

screen shot 2018-06-10 at 5 04 42 pm

The one case that would still not be handled correctly with the above approach would be a scroll anchor that is set outside of the "safe zone" but a current scroll that goes inside of the safe zone (as shown below). If the user scrolls slowly back toward the beginning of the list, it may be difficult to align the first cell with zero without introducing scroll janky.
screen shot 2018-06-10 at 5 08 26 pm

@bvaughn bvaughn self-assigned this Jun 8, 2018
@bvaughn bvaughn changed the title CellMeasurer Support just-in-time measured content Jun 10, 2018
@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Jun 17, 2018

Work in progress for MVP available at master...issues/6

Loading

@techniq
Copy link

@techniq techniq commented Jun 17, 2018

I use this within mui-downshift and currently still use UNSAFE_componentWillReceiveProps, although I plan to try to port to react-window in the near future (once the dynamic content height is available).

Loading

@luoboding
Copy link

@luoboding luoboding commented Jul 10, 2018

is this a reusable mechanism in UITableView in IOS ?

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Jul 10, 2018

@luoboding I don't understand your question. Could you elaborate?

Loading

@luoboding
Copy link

@luoboding luoboding commented Jul 12, 2018

@bvaughn sorry, my friend, my english is not very good.

i have a problem in my project, i have thousands of options in select element, it get very slow and jammed when page reload, i tried to write a component to make it better, i was a IOS developer, i know a reusable mechanism in UITableView in IOS, if i need a 500px height select element, i configure option-element's height to 100px, so i just need create (Math.floor(500 / 100)) number of option element and capacity of queue(current displayed datasource queue), when i scroll select element up or down, just push or pop into the queue to re-render it.

i want import react-window in my project, does it work like i mentioned?

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Jul 12, 2018

What you're describing is how react-window (and windowing, or occlusion culling, in general) works. It's not really related to this issue though. This is about just-in-time measuring the windowed content. In your case, objects have a fixed height– so you can use the FixedSizeList component: https://react-window.now.sh/#/examples/list/fixed-size

Loading

@yuchi
Copy link

@yuchi yuchi commented Jul 21, 2018

Nice to see anchoring natively addressed in react-window.

By the way sorry if I raised the bar a little bit too much on diagrams… 😅

Loading

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented Jul 21, 2018

@bvaughn when will you release this feature, I'm looking for that

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Jul 22, 2018

I published 1.0.0 without this functionality because I'm having trouble finding the time (and energy) to finish this right now. Still plan to add it in the future. No estimate on when.

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Jul 24, 2018

Looks like Polymer's "iron list" uses a similar technique as what I'm proposing here: https://github.com/domenic/infinite-list-study-group/blob/master/studies/Polymer-iron-list.md#virtual-list-sizing

Loading

@kevinder
Copy link

@kevinder kevinder commented Aug 10, 2018

this would be really useful for us, right now it seems like we have to keep CSS in sync with component logic that duplicates the height computation just to pass it to the virtual list.

Loading

@carlosagsmendes
Copy link

@carlosagsmendes carlosagsmendes commented Aug 29, 2018

@kevinder can you share how you are addressing just-in-time measured content? Thanks in advance

Loading

@ghost
Copy link

@ghost ghost commented Sep 26, 2018

@carlosagsmendes not sure if this will help but here is what I am doing when using this to display chat history:

1.) In the constructor create a ref for the list and for my ChatHistory component:

  constructor(props) {
    super(props);
    this.listRef = createRef();
    this.chatHistoryRef = createRef();
    this.listHeight = 0;
  }

I then pass the refs down to the ChatHistory which is responsible for rendering the list

2.) In componentDidMount of the parent component I use the ChatHistory ref to get the height of the element:

componentDidMount() {
    this.listHeight = this.chatHistoryRef.current.offsetHeight;
}

3.) In the parent component I maintain an array in it's state with the chathistory details. When adding a new item to that array I do it like this:

  // find out how many pixels in height the text is going to use
  const size = getSize({
    text: displayText,
    className: styles.message,
  });

  let { height } = size;
  height += 20; // adds some spacing in pixels between messages
...rest of items needed for the chat history item..add them all to an array and update state

getSize is based on https://github.com/schickling/calculate-size but I modified it to support accepting a class name. That class is the same one used as the container for displaying the individual messages

I'm sure there's a better way to achieve this but it seems pretty fast and I haven't hit any issues yet

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Sep 26, 2018

@osdlge: Any chance you have a demo of this anywhere I could check out– like Code Sandbox (or similar)?

Loading

@ghost
Copy link

@ghost ghost commented Sep 26, 2018

@bvaughn sure thing, I extracted the relevant parts of my code to https://codesandbox.io/s/5z282z7q1l

Loading

paradoxxxzero added a commit to Kozea/formol that referenced this issue Sep 27, 2018
@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Sep 27, 2018

Thank you for sharing! 😄

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Oct 11, 2018

I've pushed some work in progress to an initial approach on lazily measured content to a branch named issue/6

I've also deployed a demo:
https://react-window-next.now.sh/#/examples/list/dynamic-size

Loading

@carlosagsmendes
Copy link

@carlosagsmendes carlosagsmendes commented Oct 11, 2018

Very cool.

I'm still trying to make sense of the general problems with infinite scrolling while going through the issue/6 branch source and this discussion. So the question following question may not make sense, but here it goes since I would really like to understand this further:

Is the above mention to 'Scroll Anchor' related to scroll Anchoring as a technique like in the linked article or the CSS Scroll Anchoring specification?

Thanks in advance

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Oct 11, 2018

It's kind of tangentially related to your first link, but not to the second one. It's just a term I chose to use for the issue because it made sense to me, not because it's related to any other spec or proposal.

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Oct 12, 2018

I've also published a pre-release version of react-window to NPM as "next" (e.g. yarn add react-window@next).

You can play around with it by forking this Code Sandbox:
https://codesandbox.io/s/5x8vlm0o7n

Loading

@akraines
Copy link

@akraines akraines commented Oct 12, 2018

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Oct 12, 2018

Loading

@gnir-work
Copy link

@gnir-work gnir-work commented Oct 21, 2018

Hey,
I wanted to ask if there is an estimate for an official release?

My team uses react-virtualized for a new project we started however we need the functionality of scrolling up and jumping to specific row index (as you can assume our rows have dynamic content, containing images and other changing content).

Would you recommend migrating to react-window in its alpha release? Or should we wait for an official release?

Also if there is any way i can contribute to finishing this feature i would love to help out 😄

Loading

@edgars-sirokovs
Copy link

@edgars-sirokovs edgars-sirokovs commented Jun 9, 2020

@bvaughn Any plans on publishing a new alpha version on npm? We haven't had any update since 1.6.0-alpha.1 😕

Loading

@mihanizm56
Copy link

@mihanizm56 mihanizm56 commented Jul 3, 2020

@bvaughn any updates? will it be fixed in recent versions?

Loading

@WillSquire
Copy link

@WillSquire WillSquire commented Jul 8, 2020

For those wanting to use Typescript with DynamicSizeList - module augmentation can give the types needed for DynamicSizeList working off of the DefinitelyTyped definitions (a workaround to my query posted in this thread last year).

Add the follow to the project:

import React, { Component } from 'react'

declare module 'react-window' {
  export type DynamicSizeListProps = Omit<FixedSizeListProps, 'itemSize' | 'scrollToItem'>
  export class DynamicSizeList extends Component<DynamicSizeListProps> {}
}

Then import as usual:

import {
  DynamicSizeList,
  DynamicSizeListProps,
} from 'react-window'

// use as normal...

@types/react-window still needs to be installed first for the FixedSizeListProps definition.

Loading

@tony-scio
Copy link

@tony-scio tony-scio commented Jul 8, 2020

Thanks for the typedefs, going to add them now! I've been using DynamicSizeList in prod for over a year now. Currently based on this fork: "@john-osullivan/react-window-dynamic-fork": "^1.9.0-alpha.1" (is that the most up to date thing still?). I tried to switch to react-virtuoso because of the uncertainty about if/when this would actually be released. But found it less performant and stuck w/ this.

@bvaughn is it time yet to pave the cowpath and release this to npm? Maybe just renamed to ExperimentalDynamicSizeList if you're still worried about it not being ready.

Loading

@slavafomin
Copy link

@slavafomin slavafomin commented Aug 10, 2020

Hello guys! Thank you for your hard work and this great library. I'm really looking forward for this feature! However, I would suggest to add information regarding this issue to the README and documentation/examples. It took me quite some time to figure out that dynamic content is not actually supported by the library and it's only useful for items with fixed/known size. I believe the README has a nice FAQ section where this could be added.

Loading

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented Aug 10, 2020

if you are looking for a table/grid/tree, this would be a good start https://autodesk.github.io/react-base-table/examples/dynamic-row-heights, built on VariableSizeGrid

Loading

@anton6
Copy link

@anton6 anton6 commented Sep 18, 2020

@tony-scio can you please share a working codesandbox for using DynamicSizeList from 1.6.0-alpha.1 with InfiniteLoader? Thanks, I would really appreciate it.

Loading

@niklaspollonen
Copy link

@niklaspollonen niklaspollonen commented Oct 7, 2020

Has anyone gotten this to work with react-beautiful-dnd? Dragging seems to make the items jump on top of each-other.

Loading

@andrei9669
Copy link

@andrei9669 andrei9669 commented Oct 12, 2020

Has anyone gotten this to work with react-beautiful-dnd? Dragging seems to make the items jump on top of each-other.

i will also wait for this answer :D

Loading

@eyea
Copy link

@eyea eyea commented Oct 29, 2020

[HELP]
There is no code for show at this link:
dynamic-size-list-vertical
and I need this ability.
THANKS

Loading

@eyea
Copy link

@eyea eyea commented Oct 29, 2020

[HELP]
There is no code for show at this link:
dynamic-size-list-vertical
and I need this ability.
THANKS

https://react-window-next.now.sh/#/examples/list/dynamic-size

Loading

@JavaJamie
Copy link

@JavaJamie JavaJamie commented Oct 30, 2020

Any update on this? We need something similar as we can (it's usually very few, but in some cases can be a lot) render a large responsive grid list of items (their heights are dynamic and will change on mobile as various text wraps etc). I feel react-window would be a game changer if it could handle this use case. If not, are there any reliable alternatives?

Loading

@petyosi
Copy link

@petyosi petyosi commented Oct 30, 2020

Any update on this? We need something similar as we can (it's usually very few, but in some cases can be a lot) render a large responsive grid list of items (their heights are dynamic and will change on mobile as various text wraps etc). I feel react-window would be a game changer if it could handle this use case. If not, are there any reliable alternatives?

@JavaJamie since you specifically asked for alternatives - the react-virtuoso library comes with built-in support for measuring dynamic content. Disclaimer: I am the author of it.

Loading

@rafde
Copy link

@rafde rafde commented Nov 7, 2020

@JavaJamie since you specifically asked for alternatives - the react-virtuoso library comes with built-in support for measuring dynamic content. Disclaimer: I am the author of it.

react-virtuoso is also double the size of react-window. Always have to keep dependency size in mind.

https://bundlephobia.com/result?p=react-window@1.8.6
https://bundlephobia.com/result?p=react-virtuoso@0.20.1

Loading

@bvaughn
Copy link
Owner Author

@bvaughn bvaughn commented Dec 4, 2020

I have accepted the fact that I don't have the time or energy to finish this effort. If anyone would like to step in and finish the branch I started, I'd welcome your help. (Please also see issue #302 for how this would fit into version two release as the new List and Grid components.)

Loading

@burtonator
Copy link

@burtonator burtonator commented Dec 4, 2020

We ended up implementing something from scratch for this such that used visibility sensors which worked out really well.

I could probably OSS it or point you guys to the right place if you want to rip it out and create a new project or move it here.

One trick is we used 'blocks' so that we could increase performance. Basically we take each row, and actually put it into a parent block of like 25 items, then swap that in when necessary.

Loading

@plandem
Copy link

@plandem plandem commented Jan 5, 2021

@burtonator it would be really helpful

Loading

@ofrinevo
Copy link

@ofrinevo ofrinevo commented Jan 5, 2021

Can anyone provide a bug-free implementation of react-select with DynamicSizeList?
I can't get it to work well together.
Currently I face 2 problems (sorry for no screenshots):

  1. All items have style={position: absolute, left: 0, top: 0} which doesn't let me use this style since all options sit on top of each other.
  2. If I don't use the style prop, the list is shown well, but when I scroll a bit, the part of the list with options in it shrinks, while the total height remains unchanged. Thus, when I scroll, I get x pixels of actual options and length-x pixels white space.

Could not find any working example of the two.
I'll note that I use the 1.9.0 fork, on Chrome.

Edit. Found an answer here above, in the hidden comments section. #6 (comment)

Loading

@twall
Copy link

@twall twall commented Jan 21, 2021

One simplification to the scrolling issue would be to use the scrollbar to indicate the index of the item in the list instead of a pixel position, if the scroll is more than roughly one viewport height away. Instead of attempting to "scroll down" X pixels, the component simply renders the items around the desired index. Halfway down renders item at index N/2, and at bottom renders item at index N-1.

That would allow direct thumb scrolling to the middle or end of the list, without the lagging of the scroll thumb while the component renders and calculates sizes. Right now, for very long VariableSizeList components, it's nearly impossible to scroll to the bottom as the cursor drags faster than the scroll thumb moves.

Loading

@Karpengold
Copy link

@Karpengold Karpengold commented Jul 30, 2021

Is here something in progress or this feature is freezed?

Loading

@mimiqkz
Copy link

@mimiqkz mimiqkz commented Jul 30, 2021

I want to use this feature because Lighthouse keeps complain about Excessive DOM. However, it has been so many years now. Is there any alternation towards this? Another project? Since the author doesn't have much time and effort to continue this?

Loading

@gmaclennan
Copy link

@gmaclennan gmaclennan commented Jul 30, 2021

react-virtual might solve this problem for some people here. It supports dynamic measurement rendering.

Loading

@codebex
Copy link

@codebex codebex commented Jul 31, 2021

react-virtuoso is another option that has this feature.

Loading

@tawtsvenz
Copy link

@tawtsvenz tawtsvenz commented Oct 12, 2021

Would you take a look at this sandbox, I tried to create a DynamicSizeList based on VariableSizeList
https://codesandbox.io/s/dynamic-size-list-zcntp

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet