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

Question: Compatibility with react-custom-scrollbars #143

Closed
t1mmen opened this issue Mar 3, 2016 · 19 comments
Closed

Question: Compatibility with react-custom-scrollbars #143

t1mmen opened this issue Mar 3, 2016 · 19 comments

Comments

@t1mmen
Copy link

t1mmen commented Mar 3, 2016

Hi,

I opened an issue at malte-wessel/react-custom-scrollbars#30, looking for a way to leverage react-virtualized with react-custom-scrollbars.

As the author @malte-wessel mentioned, this'll require changes in both libraries. He seems keen on making the changes on his end, so I'm hopeful that I can interest you in doing the same? :)

@bvaughn
Copy link
Owner

bvaughn commented Mar 4, 2016

Interesting suggestion @t1mmen. I've commented on the issue you linked, although unless I'm missing something... I don't initially see any changes that react-custom-scrollbars would need to make.

@bvaughn
Copy link
Owner

bvaughn commented Mar 4, 2016

I have a proof-of-concept working in a branch (issues/143).

Started to integrate with react-custom-scrollbars but then realized that Grid already had all of the properties it needed (scroll top/left, client width/height, etc.). These properties are already being passed to the onScroll callback or, better yet, could be accessed via the ScrollSync component. So I put together the following demo:

untitled screencast - edited 2

The code in question is:

<ScrollSync>
  {({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) => {
    const x = scrollLeft / (scrollWidth - clientWidth)
    const y = scrollTop / (scrollHeight - clientHeight)
    return (
      <AutoSizer disableHeight>
        {({ width }) => (
          <div style={{
            backgroundColor: `rgb(${Math.round(y * 255)}, ${Math.round(x * 255)}, 255)`,
            color: `rgb(${Math.round(255 * y)}, ${Math.round(255 * y)}, ${Math.round(255 * y)})`,
            height,
            width
          }}>
            <Grid
              className={styles.BodyGrid}
              columnWidth={columnWidth}
              columnsCount={columnsCount}
              height={height}
              onScroll={onScroll}
              overscanColumnsCount={overscanColumnsCount}
              overscanRowsCount={overscanRowsCount}
              renderCell={this._renderBodyCell}
              rowHeight={rowHeight}
              rowsCount={rowsCount}
              width={width}
            />
          </div>
        )}
      </AutoSizer>
    )
  }}
</ScrollSync>

Note that this requires 2 small changes to ScrollSync:

  1. Pass through client width/height and scroll width/height (in addition to scroll top/left).
  2. Be sure to invoke child function initially with the correct width/height. (My demo has an AutoSizer nested inside of a ScrollSync and the first time the ScrollSync child is rendered, the width/height are 0.) This probably has a simple fix but it's midnight here now and I'm sleepy. :)

@t1mmen
Copy link
Author

t1mmen commented Mar 4, 2016

That's looking pretty slick, good work! I'm excited to give it a try in my project!

@bvaughn
Copy link
Owner

bvaughn commented Mar 4, 2016

Great! :)

I'll try to get it finished up this evening after work. (This is a side project for me so I can't do much with it during the day.)

@t1mmen
Copy link
Author

t1mmen commented Mar 4, 2016

I completely understand @bvaughn – no rush, just thankful you've decided to take this issue on! Appreciate your hard work 👍

bvaughn added a commit that referenced this issue Mar 5, 2016
Added additional properties to ScrollSync callback to enable scroll-driven UI changes
@bvaughn
Copy link
Owner

bvaughn commented Mar 5, 2016

@malte-wessel
Copy link

Wow, that was quick :) I'll have a look at it!

@malte-wessel
Copy link

Sadly, it looks like both libraries do not work well together :( I tried two approaches:

  1. Integrating the Scrollbars component directly into the Grid component here
    For any reason only scroll events at the end of the scrolling are recognized by the Scrollbars component. This could be due to the fact that the Scrollbars component wraps the scrollable element and passes the events from there. This approach would also assume that components like Grid would implement the Scrollbars component, which is not desired. It also introduces a new layer between Grid and the higher order components
  2. Wrap the Grid component with Scrollbars
    This doesn't work at all, since Scrollbars renders the scrollable element itself and there is currently no way to replace this behaviour.

@t1mmen Nevertheless it should be easy to implement you own scrollbars with the changes in v5.5.0. You only need to render the tracks and thumbs and update their styles like I did here

@bvaughn
Copy link
Owner

bvaughn commented Apr 19, 2016

Quick update to this issue in case anyone else sees it and is curious. react-virtualized now supports a custom renderer prop- renderCellRanges. This function is responsible for rendering (and positioning) the visible range of cells. Its signature is:

function ({
  columnMetadata:Array<Object>,
  columnStartIndex: number,
  columnStopIndex: number,
  renderCell: Function,
  rowMetadata:Array<Object>,
  rowStartIndex: number,
  rowStopIndex: number
}): Array<PropTypes.node>

Grid provides a default implementation of this (obviously) but you can override it if you want to render customized UI along with your cells (eg. gantt chart style overlays, custom scrollbars, etc). Doing it this way is a little more work but it gives you much greater flexibility in what your grid looks like and react-virtualized still manages all of the data windowing for you.

If you're curious, you can see the default implementation here: https://github.com/bvaughn/react-virtualized/blob/master/source/Grid/Grid.js#L729

@amitripshtos
Copy link

amitripshtos commented Oct 13, 2016

Hey, thank you for your help guys,
Im having hard times making these 2 packages work together, Im using react-visualized Table component and I don't have renderCellRanges to use and I`m kinda lost.

I would love for some help and love, thank you guys ! :)
Great product by the way!

@bvaughn
Copy link
Owner

bvaughn commented Oct 13, 2016

Hi @amitripshtos,

RV Table uses a Grid inside of itself and passes props down to it. It renders headers above the Grid, but those aren't scrollable anyway. So if you want custom scrollbars on the Grid- you should still be able to use the renderCellRanges approach mentioned above.

Maybe something more or less like this?

import { defaultCellRangeRenderer, Table } from 'react-virtualized'

function cellRangeRenderer (props) {
  const children = defaultCellRangeRenderer(props) // Render Table rows
  children.push(
    // Push custom scrollbar container
  )
  return children
}

function renderTableWithCustomScrollbars (tableProps) {
  return (
    <Table
      {...tableProps}
      cellRangeRenderer={cellRangeRenderer}
      gridStyle={{
        // I'm unsure about this part because
        // I'm unfamiliar with react-custom-scrollbars
        overflowY: 'hidden'
      }}
    />
  )
}

@achrist
Copy link

achrist commented Mar 21, 2017

@amitripshtos Were you able to get these two packages to work together? Also having troubles trying to get these to work and can't quite figure it out.

@amitripshtos
Copy link

Actually I did not, just gave up and used normal scrollbar :)

@goornaz
Copy link

goornaz commented Apr 7, 2017

Hello to all, and sorry for my english

I tried to integrate the list of react-virtualized with react-custom scrollbars.
I solved this way, maybe just a little bit tricky :D

There are two div. One with the list and one with the scrollbar.
Scrollbar's div have height = itemsCount * itemHeight in order to have the same content as List.

So, i connected the two components' scroll action. When you scroll from List, also scrollbar's div scrolls. When you scroll from scrollbar, also the List scroll.

import React from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import {
  AutoSizer,
  List,
} from 'react-virtualized';
import scrollbarSize from 'dom-helpers/util/scrollbarSize';

type Props = {
  items: Array<*>,
  height: number,
  rowRender: Function,
  rowHeight: number,
  lightScrollbar?: boolean,
}

type State = {
  scrollTop: number,
}

export default class VirtualizedListScroll extends React.Component {
  props: Props;
  state: State;

  constructor(props: Props) {
    super(props);
    this.state = {
      scrollTop: 0,
    };
  }

  customScrollBar = null;

  handleCustomScroll = (event: Object) => {
    if (event) {
      const { target } = event;
      if (!(target instanceof HTMLDivElement)) {
        return;
      }
      this.setState({ scrollTop: target.scrollTop });
    }
  }

  handleVirtualizedScroll = (event: Object) => {
    if (event && event.scrollTop) {
      this.setState({ scrollTop: event.scrollTop });
      if (this.customScrollBar) {
        this.customScrollBar.scrollTop(event.scrollTop);
      }
    }
  }

  render() {
    const propsHeight = this.props.height;
    const itemsCount = this.props.items.length || 0;
    const rowRender = this.props.rowRender;
    const rowHeight = this.props.rowHeight;
    const scrollbarColor = this.props.lightScrollbar ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.2)';
    const scrollBarWidth = ((itemsCount * rowHeight) > propsHeight) ? scrollbarSize() : 0;
    return (
      <AutoSizer disableHeight>
        {({ width }) => (
          <div className="VirtualizedScroll" style={{ heigth: `${propsHeight}px`, width }}>
            <List
              style={{ zIndex: '1', position: 'absolute' }}
              {...this.props}
              width={width + scrollBarWidth}
              height={propsHeight}
              scrollTop={this.state.scrollTop}
              rowCount={itemsCount}
              rowHeight={rowHeight}
              onScroll={this.handleVirtualizedScroll}
              rowRenderer={rowRender}
            />
            <Scrollbars
              className="ScrollContent"
              style={{ height: `${propsHeight}px`, width: scrollbarSize() }}
              onScroll={this.handleCustomScroll}
              ref={scrollbars => { this.customScrollBar = scrollbars; }}
              renderThumbVertical={({ style }) => <div style={{ ...style, backgroundColor: scrollbarColor, borderRadius: '3px' }} />}
            >
              <div className="ScrollBarContainer" style={{ height: (itemsCount * rowHeight) }} />
            </Scrollbars>
          </div>
        )}
      </AutoSizer>
    );
  }
}

And this is the CSS

.VirtualizedScroll {
  position: relative;
  overflow-x: hidden;
}

.ScrollContent {
  background: transparent;
  z-index: 2;
  float: right;
}

.ScrollBarContainer {
  width: auto;
}

@5angel
Copy link
Contributor

5angel commented Jun 2, 2017

@enjoyyournoise yeah, yours is too complicated and not that efficient.

I've found that you can just rewrite the Grid's _onScroll method and it will work like a charm. Actually, the only thing preventing on using inner _onScroll in the first place is container node check.

@bvaughn can you maybe add a second parameter to _onScroll that let you pass event from another container?

@5angel
Copy link
Contributor

5angel commented Nov 22, 2017

@bvaughn Hey! I'm back at it again.

Seems like integrating react-custom-scrollbars with Table is a bit tricky, since the Grid component, to which this package should be applied, is obscured inside. Might you suggest way to do it properly?

@MrMint
Copy link

MrMint commented Dec 19, 2017

Was hoping to be able to get custom scrollbars into the table as well. @5angel did you come up with/get any suggestions for solutions around how to do this cleanly? I took a quick peek at the source and it didn't look like there was any easy way to get at wrapping the grid component in the table.

@5angel
Copy link
Contributor

5angel commented Dec 19, 2017

@MrMint yeah, that was my first thought too. Well, there is a way—you might want to add gridrenderer prop, which will pass through the Grid element, allowing you to wrap it in custom scrollbars. That's still a bit hacky and not really useful outside that particular use case, but seems feasible at least.

That's why I asked @bvaughn if he has some thoughts already.

@kirillku
Copy link

kirillku commented Mar 28, 2018

Got react-custom-scrollbars working with react-virtualized.Table using windowScroller. Had to make headerRow fixed to have it always on top, also it should always have background.

Here is the demo: https://codesandbox.io/s/32m23wyo9m

UPD: realized that windowScroller is not needed, only autoHeight prop is enough, so removed it. https://codesandbox.io/s/5vvq2wpln4

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

No branches or pull requests

9 participants