0

Description

I have a potentially long list of items rendered in a react-virtualized VirtualScroll.
Each item (row) in the list has a fairly large amount of elements, one of which opens a context menu. I'm trying to use react-tether to render that menu on the HTML body (so that it's not hidden when the item is at the bottom/top of the scrollable list) and keep the menu 'stuck' to my item while the user scrolls through the list.
My problem is that there is a noticeable lag in updating the position of the tethered menu.

Some of the steps I've taken so far:

  1. Rendered a simple list, without VirtualScroll. The tethered menu was rendered smoothly, no noticeable jank. That's how I concluded that the problem is with react-virtualized
  2. Simplified my rowRenderer down to only the menu trigger, as recommended here.
  3. Implemented shouldComponentUpdate in the row component. This has improved the perceived performance greatly, reduced the delay greatly but it is still noticeable.
  4. Checked Chrome devtools' timeline. I see reflows triggered by both Grid.js and tether.js.

Library Versions:

  • react-virtualized v. 7.24.3 (Large project, not ready to make the step to 8.x yet)
  • react-tether v. 0.5.2
  • react v. 15.2.1

Working Demo

https://plnkr.co/edit/f7OhCoCXkDsWbyjxhR3f

Screenshot:

screenshot

Community
  • 1
  • 1
burtyish
  • 1,033
  • 2
  • 13
  • 27
  • "_Checked Chrome devtools' timeline. I see reflows triggered by both Grid.js and tether.js._" - Not sure about tether, but in react-virtualized v8 I made 2 important perf optimizations to the up-stream `detect-element-resize` library that prevented it from doing a lot of unnecessary reflows. If you're using `AutoSizer`, this change may help you. (If you're not then it would not be relevant.) I'd be happy to take a look at your issue if you could share some code with me- even if it's just a small Plnkr. – bvaughn Oct 27 '16 at 15:22
  • @brianvaughn thanks for the quick reply. 1. I do not use `AutoSizer`, but rather our own custom resize handler. If it comes to that, I may try to upgrade to 8.x and switch to `AutoSizer` 2. I'll see if I can set up a shareable example. Probably a good idea anyway to isolate the issue – burtyish Oct 27 '16 at 15:39
  • @brianvaughn I updated my question with a link to working demo on splunkr. The delay is visible. – burtyish Oct 27 '16 at 20:20

1 Answers1

0

FYI that Plnkr was broken. It was including the wrong styles version (8.x instead of 7.x).

After fixing that, the visual "lag" I see is not something I know how to fix with version 7.x. The problem is that the browser manages the scroll in a different thread (so that JS doesn't block it and cause the UI to feel unresponsive). Normally this isn't noticeable because all of the UI is being scrolled together, however in this case- your modal is absolutely positioned, by JS, and so it sometimes lags behind the browser's scroll position.

That being said, upgrading to version 8 gives you an alternative to the portal approach, which does fix the issue, as shown in this updated plunk: https://plnkr.co/edit/NESPMzDz22JjwFVthve4?p=preview

They key is this:

render() {
  const { menuOpen } = this.state;
  const { index, style } = this.props;

  // Make sure open cells are on a higher z-index than others
  if (menuOpen) {
    style.zIndex = 2;
  }

  return (
    <div
      className="row"
      style={style}
    >
      {list[index]}

      {/* Render your button OR menu item here */}
    </div>
  );
}
bvaughn
  • 13,300
  • 45
  • 46
  • Thanks for the suggestion! I could use your suggestion if I added the ability to figure out the position of my element relative to the edges of it's 'scroll parent', and flip the menu vertically if it's too close to the bottom edge. – burtyish Oct 29 '16 at 12:38
  • My initial motivation to take the portal approach was to prevent the menu from being hidden by the edges of the list (which has `overflow-y: hidden`), so it could be fully visible below even the last row. It is my understanding that `tether.js` is optimized to update the element's location via JS _while scrolling_. – burtyish Oct 29 '16 at 12:42
  • I don't know of any way to achieve what you're aiming for- (eliminating any possible source of scroll lag)- while using a portal technique like tether.js uses. The best you can hope for is to performance optimize as much as possible to reduce the lag. To that end, I encourage you to consider upgrading to react-virtualized v8 as that version includes more performance optimizations. I understand your concern about overflowing the edges of the parent `List`. Unfortunately that is a limitation of the approach I mentioned. It may not fit your needs. :/ – bvaughn Oct 29 '16 at 23:33
  • I ended up disabling the scroll when the submenu is open. This is aligned with the behavior with native select inputs. Your answer helped me by confirming that the performance issue I was seeing cannot be avoided. Thanks! – burtyish Nov 01 '16 at 08:22
  • with [popper.js](https://popper.js.org) you could position the element directly inside the scrollable container thus avoiding all sources of lag. Check out the v1-beta. Disclaimer, I'm the author of the library – Fez Vrasta Dec 28 '16 at 23:36