-
-
Save bvaughn/3a358dda3654e1e93fba35890a093c19 to your computer and use it in GitHub Desktop.
| /** @flow */ | |
| import Immutable from 'immutable' | |
| import React, { Component, PropTypes } from 'react' | |
| import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox' | |
| import { LabeledInput, InputRow } from '../demo/LabeledInput' | |
| import AutoSizer from '../AutoSizer' | |
| import Grid from './Grid' | |
| import shallowCompare from 'react-addons-shallow-compare' | |
| import cn from 'classnames' | |
| import styles from './Grid.example.css' | |
| const FIXED_CELL_ZINDEX = 2 | |
| const FIXED_LEFT_COLUMN_WIDTH = 50 | |
| const FIXED_TOP_ROW_HEIGHT = 40 | |
| export default class GridExample extends Component { | |
| static propTypes = { | |
| list: PropTypes.instanceOf(Immutable.List).isRequired | |
| } | |
| constructor (props, context) { | |
| super(props, context) | |
| this.state = { | |
| columnWidth: 100, | |
| columnCount: 1000, | |
| height: 300, | |
| rowHeight: 40, | |
| rowCount: 1000, | |
| scrollToColumn: undefined, | |
| scrollToRow: undefined, | |
| useDynamicRowHeight: false | |
| } | |
| this._cellRangeRenderer = this._cellRangeRenderer.bind(this) | |
| this._cellRenderer = this._cellRenderer.bind(this) | |
| this._getColumnWidth = this._getColumnWidth.bind(this) | |
| this._getRowClassName = this._getRowClassName.bind(this) | |
| this._getRowHeight = this._getRowHeight.bind(this) | |
| } | |
| render () { | |
| const { | |
| columnCount, | |
| height, | |
| rowHeight, | |
| rowCount, | |
| scrollToColumn, | |
| scrollToRow, | |
| useDynamicRowHeight | |
| } = this.state | |
| return ( | |
| <ContentBox {...this.props}> | |
| <ContentBoxHeader | |
| text='Grid' | |
| sourceLink='https://github.com/bvaughn/react-virtualized/blob/master/source/Grid/Grid.example.js' | |
| docsLink='https://github.com/bvaughn/react-virtualized/blob/master/docs/Grid.md' | |
| /> | |
| <AutoSizer disableHeight> | |
| {({ width }) => ( | |
| <Grid | |
| cellRangeRenderer={this._cellRangeRenderer} | |
| cellRenderer={this._cellRenderer} | |
| className={styles.BodyGrid} | |
| columnWidth={this._getColumnWidth} | |
| columnCount={columnCount} | |
| height={height} | |
| overscanColumnCount={0} | |
| overscanRowCount={0} | |
| rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight} | |
| rowCount={rowCount} | |
| scrollToColumn={scrollToColumn} | |
| scrollToRow={scrollToRow} | |
| width={width} | |
| /> | |
| )} | |
| </AutoSizer> | |
| </ContentBox> | |
| ) | |
| } | |
| shouldComponentUpdate (nextProps, nextState) { | |
| return shallowCompare(this, nextProps, nextState) | |
| } | |
| // Forked from defaultCellRangeRenderer.js | |
| _cellRangeRenderer ({ | |
| cellCache, | |
| cellRenderer, | |
| columnSizeAndPositionManager, | |
| columnStartIndex, | |
| columnStopIndex, | |
| isScrolling, | |
| rowSizeAndPositionManager, | |
| rowStartIndex, | |
| rowStopIndex, | |
| scrollLeft, | |
| scrollTop | |
| }) { | |
| const renderedCells = [] | |
| // Top-left corner piece | |
| renderedCells.push( | |
| <div | |
| key='fixed-fixed' | |
| className={cn('Grid__cell', styles.cell, styles.topLeftCell)} | |
| style={{ | |
| height: FIXED_TOP_ROW_HEIGHT, | |
| left: scrollLeft, | |
| position: 'fixed', | |
| top: scrollTop, | |
| width: FIXED_LEFT_COLUMN_WIDTH, | |
| zIndex: FIXED_CELL_ZINDEX + 1 | |
| }} | |
| > | |
| | |
| </div> | |
| ) | |
| // Render fixed header row | |
| for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) { | |
| let columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex) | |
| renderedCells.push( | |
| <div | |
| key={`fixed-${columnIndex}`} | |
| className={cn('Grid__cell', styles.cell, styles.headerCell)} | |
| style={{ | |
| height: FIXED_TOP_ROW_HEIGHT, | |
| left: columnDatum.offset + FIXED_LEFT_COLUMN_WIDTH, | |
| position: 'fixed', | |
| top: scrollTop, | |
| width: columnDatum.size, | |
| zIndex: FIXED_CELL_ZINDEX | |
| }} | |
| > | |
| H{columnIndex} | |
| </div> | |
| ) | |
| } | |
| // Render fixed left column | |
| for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { | |
| let rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex) | |
| let datum = this._getDatum(rowIndex) | |
| renderedCells.push( | |
| <div | |
| key={`${rowIndex}-fixed`} | |
| className={cn('Grid__cell', styles.cell, styles.letterCell)} | |
| style={{ | |
| backgroundColor: datum.color, | |
| height: rowDatum.size, | |
| left: scrollLeft, | |
| position: 'fixed', | |
| top: rowDatum.offset + FIXED_TOP_ROW_HEIGHT, | |
| width: FIXED_LEFT_COLUMN_WIDTH, | |
| zIndex: FIXED_CELL_ZINDEX | |
| }} | |
| > | |
| {datum.name.charAt(0)} | |
| </div> | |
| ) | |
| } | |
| // Forked from defaultCellRangeRenderer.js | |
| for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { | |
| let rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex) | |
| for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) { | |
| let columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex) | |
| let key = `${rowIndex}-${columnIndex}` | |
| let renderedCell | |
| // Avoid re-creating cells while scrolling. | |
| // This can lead to the same cell being created many times and can cause performance issues for "heavy" cells. | |
| // If a scroll is in progress- cache and reuse cells. | |
| // This cache will be thrown away once scrolling complets. | |
| if (isScrolling) { | |
| if (!cellCache[key]) { | |
| cellCache[key] = cellRenderer({ | |
| columnIndex, | |
| isScrolling, | |
| rowIndex | |
| }) | |
| } | |
| renderedCell = cellCache[key] | |
| // If the user is no longer scrolling, don't cache cells. | |
| // This makes dynamic cell content difficult for users and would also lead to a heavier memory footprint. | |
| } else { | |
| renderedCell = cellRenderer({ | |
| columnIndex, | |
| isScrolling, | |
| rowIndex | |
| }) | |
| } | |
| if (renderedCell == null || renderedCell === false) { | |
| continue | |
| } | |
| let child = ( | |
| <div | |
| key={key} | |
| className='Grid__cell' | |
| style={{ | |
| height: rowDatum.size, | |
| left: columnDatum.offset + FIXED_LEFT_COLUMN_WIDTH, | |
| position: 'fixed', | |
| top: rowDatum.offset + FIXED_TOP_ROW_HEIGHT, | |
| width: columnDatum.size | |
| }} | |
| > | |
| {renderedCell} | |
| </div> | |
| ) | |
| renderedCells.push(child) | |
| } | |
| } | |
| return renderedCells | |
| } | |
| _cellRenderer ({ columnIndex, rowIndex }) { | |
| const rowClass = this._getRowClassName(rowIndex) | |
| const datum = this._getDatum(rowIndex) | |
| let content | |
| switch (columnIndex) { | |
| case 0: | |
| content = datum.name | |
| break | |
| case 1: | |
| content = datum.random | |
| break | |
| default: | |
| content = ( | |
| <div> | |
| c:{columnIndex} | |
| <br /> | |
| r:{rowIndex} | |
| </div> | |
| ) | |
| break | |
| } | |
| const classNames = cn(rowClass, styles.cell, { | |
| [styles.centeredCell]: columnIndex > 2 | |
| }) | |
| return ( | |
| <div className={classNames}> | |
| {content} | |
| </div> | |
| ) | |
| } | |
| _getColumnWidth ({ index }) { | |
| switch (index) { | |
| case 0: | |
| return 100 | |
| case 1: | |
| return 300 | |
| default: | |
| return 50 | |
| } | |
| } | |
| _getDatum (index) { | |
| const { list } = this.props | |
| return list.get(index % list.size) | |
| } | |
| _getRowClassName (row) { | |
| return row % 2 === 0 ? styles.evenRow : styles.oddRow | |
| } | |
| _getRowHeight ({ index }) { | |
| return this._getDatum(index).size | |
| } | |
| } |
If anyone gets to this example and they are facing scrolling issues, its probably because of the position: fixed property in the _cellRangeRenderer cells. Update those to postion: absolute and scrolling should be good
Places where it needs updating -
The fixed cells flicker because scrollLeft, scrollTop are not updated smoothly by the browser.
Using pure logic scrolling seems to be the only way to resolve the flickering and achieve a smooth scrolling experience.
I ended up using
https://github.com/gooddata/zynga-scroller-es6 and wrapped the grid in a component similar to https://github.com/facebook/fixed-data-table/blob/master/site/examples/TouchableArea.js
Hey @vinayaknagpal. Any chance you'd be willing to share a Gist or Plunker of your react-virtualized + zynga-scroller-es6 combination? 😄
@bvaughn
Sure. Will put together an example and share in a few days. Racing a release deadline at the moment.
CSS here: