// @flow import React, { Component } from 'react'; type ScrollbarUpdateContext = { size: boolean, position: boolean, }; export default class CustomScrollbars extends Component { props: { thumbInset: { x: number, y: number }, children: ?React.Element<*>, }; static defaultProps = { thumbInset: { x: 2, y: 2 }, }; _scrollableElement: ?HTMLElement; _thumbElement: ?HTMLElement; componentDidMount() { this._updateScrollbarsHelper({ position: true, size: true }); } componentDidUpdate() { this._updateScrollbarsHelper({ position: true, size: true }); } render() { return (
{ this.props.children }
); } _onScrollableRef = (ref) => { this._scrollableElement = ref; } _onThumbRef = (ref) => { this._thumbElement = ref; } _onScroll = () => { this._updateScrollbarsHelper({ position: true }); } _computeThumbPosition(scrollable: HTMLElement, thumb: HTMLElement) { // the content height of the scroll view const scrollHeight = scrollable.scrollHeight; // the visible height of the scroll view const visibleHeight = scrollable.offsetHeight; // scroll offset const scrollTop = scrollable.scrollTop; // lowest point of scrollTop const maxScrollTop = scrollHeight - visibleHeight; // calculate scroll position within 0..1 range const scrollPosition = scrollHeight > 0 ? scrollTop / maxScrollTop : 0; const thumbHeight = thumb.clientHeight; // calculate the thumb boundary to make sure that the visual appearance of // a thumb at lowest point matches the bottom of scrollable view const thumbBoundary = visibleHeight - thumbHeight - (this.props.thumbInset.y * 2); // calculate thumb position based on scroll progress and thumb boundary // adding vertical inset to adjust the thumb's appearance const thumbPosition = (thumbBoundary * scrollPosition) + this.props.thumbInset.y; return { x: -this.props.thumbInset.x, y: thumbPosition, }; } _computeThumbHeight(scrollable: HTMLElement) { const scrollHeight = scrollable.scrollHeight; const visibleHeight = scrollable.offsetHeight; const thumbHeight = (visibleHeight / scrollHeight) * visibleHeight; // ensure that the scroll thumb doesn't shrink to nano size return Math.max(thumbHeight, 8); } _updateScrollbarsHelper(updateFlags: $Shape) { const scrollable = this._scrollableElement; const thumb = this._thumbElement; if(scrollable && thumb) { this._updateScrollbars(scrollable, thumb, updateFlags); } } _updateScrollbars(scrollable: HTMLElement, thumb: HTMLElement, context: $Shape) { if(context.size) { const thumbHeight = this._computeThumbHeight(scrollable); thumb.style.setProperty('height', thumbHeight + 'px'); // hide thumb when there is nothing to scroll if(thumbHeight < scrollable.offsetHeight) { thumb.style.setProperty('opacity', '1'); } else { thumb.style.setProperty('opacity', '0'); } } if(context.position) { const { x, y } = this._computeThumbPosition(scrollable, thumb); thumb.style.setProperty('transform', `translate(${x}px, ${y}px)`); } } }