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

Add handleScroll for handling external scroll events #693

Merged
merged 2 commits into from
Jun 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/Grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ This method can be used to safely scroll back to a cell that a user has scrolled
Scroll to the specified offset(s).
Useful for animating position changes.

##### handleScroll ({ scrollLeft, scrollTop })

Handle scroll event.
Can be used to pass an external scroll event.

### Class names

The Grid component supports the following static class names
Expand Down
125 changes: 66 additions & 59 deletions source/Grid/Grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,71 @@ export default class Grid extends PureComponent {
this._setScrollPosition({ scrollLeft, scrollTop })
}

/**
* Handle scroll event.
* Can be used to pass an external scroll event.
*/
handleScroll ({
scrollLeft: eventScrollLeft,
scrollTop: eventScrollTop
}) {
// Prevent pointer events from interrupting a smooth scroll
this._debounceScrollEnded()

const { autoHeight, autoWidth, height, width } = this.props

// When this component is shrunk drastically, React dispatches a series of back-to-back scroll events,
// Gradually converging on a scrollTop that is within the bounds of the new, smaller height.
// This causes a series of rapid renders that is slow for long lists.
// We can avoid that by doing some simple bounds checking to ensure that scroll offsets never exceed their bounds.
const scrollbarSize = this._scrollbarSize
const totalRowsHeight = this._rowSizeAndPositionManager.getTotalSize()
const totalColumnsWidth = this._columnSizeAndPositionManager.getTotalSize()
const scrollLeft = Math.min(Math.max(0, totalColumnsWidth - width + scrollbarSize), eventScrollLeft)
const scrollTop = Math.min(Math.max(0, totalRowsHeight - height + scrollbarSize), eventScrollTop)

// Certain devices (like Apple touchpad) rapid-fire duplicate events.
// Don't force a re-render if this is the case.
// The mouse may move faster then the animation frame does.
// Use requestAnimationFrame to avoid over-updating.
if (
this.state.scrollLeft !== scrollLeft ||
this.state.scrollTop !== scrollTop
) {
// Track scrolling direction so we can more efficiently overscan rows to reduce empty space around the edges while scrolling.
// Don't change direction for an axis unless scroll offset has changed.
const scrollDirectionHorizontal = scrollLeft !== this.state.scrollLeft
? scrollLeft > this.state.scrollLeft
? SCROLL_DIRECTION_FORWARD
: SCROLL_DIRECTION_BACKWARD
: this.state.scrollDirectionHorizontal
const scrollDirectionVertical = scrollTop !== this.state.scrollTop
? scrollTop > this.state.scrollTop
? SCROLL_DIRECTION_FORWARD
: SCROLL_DIRECTION_BACKWARD
: this.state.scrollDirectionVertical

const newState = {
isScrolling: true,
scrollDirectionHorizontal,
scrollDirectionVertical,
scrollPositionChangeReason: SCROLL_POSITION_CHANGE_REASONS.OBSERVED
}

if (!autoHeight) {
newState.scrollTop = scrollTop
}

if (!autoWidth) {
newState.scrollLeft = scrollLeft
}

this.setState(newState)
}

this._invokeOnScrollMemoizer({ scrollLeft, scrollTop, totalColumnsWidth, totalRowsHeight })
}

componentDidMount () {
const { getScrollbarSize, scrollLeft, scrollToColumn, scrollTop, scrollToRow } = this.props

Expand Down Expand Up @@ -1121,64 +1186,6 @@ export default class Grid extends PureComponent {
return
}

// Prevent pointer events from interrupting a smooth scroll
this._debounceScrollEnded()

const { autoHeight, autoWidth, height, width } = this.props

const {
scrollLeft: eventScrollLeft,
scrollTop: eventScrollTop
} = event.target

// When this component is shrunk drastically, React dispatches a series of back-to-back scroll events,
// Gradually converging on a scrollTop that is within the bounds of the new, smaller height.
// This causes a series of rapid renders that is slow for long lists.
// We can avoid that by doing some simple bounds checking to ensure that scroll offsets never exceed their bounds.
const scrollbarSize = this._scrollbarSize
const totalRowsHeight = this._rowSizeAndPositionManager.getTotalSize()
const totalColumnsWidth = this._columnSizeAndPositionManager.getTotalSize()
const scrollLeft = Math.min(Math.max(0, totalColumnsWidth - width + scrollbarSize), eventScrollLeft)
const scrollTop = Math.min(Math.max(0, totalRowsHeight - height + scrollbarSize), eventScrollTop)

// Certain devices (like Apple touchpad) rapid-fire duplicate events.
// Don't force a re-render if this is the case.
// The mouse may move faster then the animation frame does.
// Use requestAnimationFrame to avoid over-updating.
if (
this.state.scrollLeft !== scrollLeft ||
this.state.scrollTop !== scrollTop
) {
// Track scrolling direction so we can more efficiently overscan rows to reduce empty space around the edges while scrolling.
// Don't change direction for an axis unless scroll offset has changed.
const scrollDirectionHorizontal = scrollLeft !== this.state.scrollLeft
? scrollLeft > this.state.scrollLeft
? SCROLL_DIRECTION_FORWARD
: SCROLL_DIRECTION_BACKWARD
: this.state.scrollDirectionHorizontal
const scrollDirectionVertical = scrollTop !== this.state.scrollTop
? scrollTop > this.state.scrollTop
? SCROLL_DIRECTION_FORWARD
: SCROLL_DIRECTION_BACKWARD
: this.state.scrollDirectionVertical

const newState = {
isScrolling: true,
scrollDirectionHorizontal,
scrollDirectionVertical,
scrollPositionChangeReason: SCROLL_POSITION_CHANGE_REASONS.OBSERVED
}

if (!autoHeight) {
newState.scrollTop = scrollTop
}
if (!autoWidth) {
newState.scrollLeft = scrollLeft
}

this.setState(newState)
}

this._invokeOnScrollMemoizer({ scrollLeft, scrollTop, totalColumnsWidth, totalRowsHeight })
this.handleScroll(event.target)
}
}