-
-
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: