Skip to content

Instantly share code, notes, and snippets.

@latviancoder
Created October 9, 2018 17:47
Show Gist options
  • Save latviancoder/0d8c26fe6e2cc3629f98f972c91f638d to your computer and use it in GitHub Desktop.
Save latviancoder/0d8c26fe6e2cc3629f98f972c91f638d to your computer and use it in GitHub Desktop.
import React from 'react';
import ReactDOM from 'react-dom';
interface InfiniteScrollerProps {
scrollingPosition?: number;
onPositionChanged: (position: number) => void;
}
class InfiniteScroller extends React.Component<InfiniteScrollerProps> {
domChildren: Array<Element>;
timeout: NodeJS.Timer;
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
if (this.props.scrollingPosition) {
this.scrollToElementCentered(this.domChildren[this.props.scrollingPosition]);
} else {
this.scrollToTop();
}
}
componentWillUnmount() {
clearTimeout(this.timeout);
window.removeEventListener('scroll', this.handleScroll);
}
render() {
this.domChildren = [];
// Get DOM nodes of the children elements
return <div>
{React.Children.map(this.props.children, (child: React.ReactElement<{}>) => {
return <div
ref={(childDiv: HTMLDivElement) => {
if (childDiv) {
this.domChildren.push(ReactDOM.findDOMNode(childDiv));
}
}}
>
{child}
</div>;
})}
</div>;
}
private scrollToTop() {
window.scrollTo(0, 0);
}
private scrollToElementCentered(element: Element) {
const elementRect = element.getBoundingClientRect();
const absoluteElementTop = elementRect.top + window.pageYOffset;
const middle = absoluteElementTop - (window.innerHeight / 2);
window.scrollTo(0, middle);
}
private handleScroll = () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(this.checkWhichChildrenAreVisibleOnScreen, 500);
};
private checkWhichChildrenAreVisibleOnScreen = () => {
let visibleChildrenIndexes: Array<number> = [];
this.domChildren.map((domChild, index) => {
if (this.isElementVisibleOnScreen(domChild)) {
visibleChildrenIndexes.push(index);
}
});
let currentPosition;
if (visibleChildrenIndexes.length > 0 && visibleChildrenIndexes.length <= 2) {
// Take first child when only 2 or less are visible on screen
currentPosition = visibleChildrenIndexes[0];
} else {
// Take the child in the middle when 3 or more are visible on the screen
currentPosition = visibleChildrenIndexes[Math.floor(visibleChildrenIndexes.length / 2)];
}
this.props.onPositionChanged(currentPosition || 0);
};
private isElementVisibleOnScreen(el: Element) {
let rect = el.getBoundingClientRect();
let viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
}
export default InfiniteScroller;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment