UNPKG

chowa

Version:

UI component library based on React

342 lines (341 loc) 12.4 kB
/** * @license chowa v1.1.3 * * Copyright (c) Chowa Techonlogies Co.,Ltd.(http://www.chowa.cn). * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const React = require("react"); const PropTypes = require("prop-types"); const resize_observer_polyfill_1 = require("resize-observer-polyfill"); const classnames_1 = require("classnames"); const utils_1 = require("../utils"); const transition_1 = require("../transition"); class Scrollbar extends React.PureComponent { constructor(props) { super(props); this.thumbTimer = null; this.scrollingTimer = null; this.state = { clientHeight: 0, clientWidth: 0, scrollHeight: 0, scrollWidth: 0, verticalThumbBtnHeight: 0, verticalThumbHeight: 0, horizontalThumbBtnWidth: 0, horizontalThumbWidth: 0, left: 0, top: 0, startX: 0, startY: 0, direction: undefined, showThumb: !props.autoHideThumb }; [ 'onDragStartHandler', 'onDragingHandler', 'onDragEndHandlr', 'onMouseWheel', 'onMouseEnterHandler', 'onMouseLeaveHandler', 'scrollTop', 'scrollToTop', 'scrollLeft', 'scrollToLeft', 'scrollToBottom', 'scrollToRight', 'getScrollWidth', 'getScrollHeight', 'getClientWidth', 'getClientHeight', 'getValues', 'getSize' ].forEach((fn) => { this[fn] = this[fn].bind(this); }); } scrollTop(nextTop) { const { top, left } = this.state; this.easeInMove(nextTop - top, (val) => { this.normalize(top + val, left); }); } scrollToTop() { const { top, left } = this.state; this.easeInMove(top, (val) => { this.normalize(top - val, left); }); } scrollLeft(nextLeft) { const { top, left } = this.state; this.easeInMove(nextLeft - left, (val) => { this.normalize(top, left + val); }); } scrollToLeft() { const { top, left } = this.state; this.easeInMove(left, (val) => { this.normalize(top, left - val); }); } scrollToBottom() { const { top, left, clientHeight, scrollHeight } = this.state; this.easeInMove(scrollHeight - clientHeight - top, (val) => { this.normalize(top + val, left); }); } scrollToRight() { const { top, left, clientWidth, scrollWidth } = this.state; this.easeInMove(scrollWidth - clientWidth - left, (val) => { this.normalize(top, left + val); }); } easeInMove(start, cb) { this.animId = utils_1.easeIn(start, cb); } getScrollWidth() { const { scrollWidth } = this.state; return scrollWidth; } getScrollHeight() { const { scrollHeight } = this.state; return scrollHeight; } getClientHeight() { const { clientHeight } = this.state; return clientHeight; } getClientWidth() { const { clientWidth } = this.state; return clientWidth; } getValues() { const { top, left, clientHeight, clientWidth, scrollHeight, scrollWidth } = this.state; return { scrollTop: top, scrollLeft: left, clientWidth, clientHeight, scrollHeight, scrollWidth }; } onDragStartHandler(direction, e) { this.setState({ startX: e.clientX, startY: e.clientY, direction }); e.preventDefault(); e.stopPropagation(); } onDragingHandler(e) { const { top, left, direction, startX, startY, scrollWidth, scrollHeight, verticalThumbHeight, horizontalThumbWidth } = this.state; if (!['vertical', 'horizontal'].includes(direction)) { return; } let nextTop = top; let nextLeft = left; if (direction === 'vertical') { const thumbTop = verticalThumbHeight * top / scrollHeight; const yMove = e.clientY - startY; nextTop = scrollHeight * (thumbTop + yMove) / verticalThumbHeight; this.normalize(nextTop, left); } else { const thumbLeft = horizontalThumbWidth * left / scrollWidth; const xMove = e.clientX - startX; nextLeft = scrollWidth * (thumbLeft + xMove) / horizontalThumbWidth; this.normalize(top, nextLeft); } this.setState({ startX: e.clientX, startY: e.clientY }); e.preventDefault(); e.stopPropagation(); } normalize(nextTop, nextLeft) { const { clientWidth, clientHeight, scrollWidth, scrollHeight } = this.state; const { onScroll, onScrollStart, onScrollStop } = this.props; if (clientWidth === 0 || clientHeight === 0) { return this.getSize(); } if (this.scrollingTimer === null) { if (onScrollStart) { onScrollStart(); } } if (onScroll) { onScroll(); } this.clearScrollingTimer(); this.scrollingTimer = window.setTimeout(() => { if (onScrollStop) { onScrollStop(); } this.clearScrollingTimer(); }, 50); if (nextTop > scrollHeight - clientHeight) { nextTop = scrollHeight - clientHeight; } if (nextLeft > scrollWidth - clientWidth) { nextLeft = scrollWidth - clientWidth; } this.setState({ top: nextTop < 0 ? 0 : nextTop, left: nextLeft < 0 ? 0 : nextLeft }); } onMouseWheel(e) { const { wheelSpeed } = this.props; const { top, left } = this.state; const { clientHeight, scrollHeight, clientWidth, scrollWidth } = this.getValues(); const scrollY = e.deltaY > 0 ? wheelSpeed : -1 * wheelSpeed; const scrollX = e.deltaX > 0 ? wheelSpeed : -1 * wheelSpeed; const nextTop = top + scrollY; const nextLeft = left + scrollX; this.normalize(nextTop, nextLeft); if ((e.deltaY < 0 && nextTop > 0) || (e.deltaY > 0 && nextTop + clientHeight < scrollHeight) || (e.deltaX < 0 && nextLeft > 0) || (e.deltaX > 0 && nextLeft + clientWidth < scrollWidth)) { e.preventDefault(); e.stopPropagation(); } } onDragEndHandlr(e) { this.setState({ direction: undefined, startX: 0, startY: 0 }); e.preventDefault(); e.stopPropagation(); } onMouseEnterHandler() { if (!this.props.autoHideThumb) { return; } this.clearThumbTimer(); this.setState({ showThumb: true }); } onMouseLeaveHandler() { if (!this.props.autoHideThumb) { return; } this.thumbTimer = window.setTimeout(() => { this.setState({ showThumb: false }); }, this.props.autoHideThumbDelay); } getSize() { const containerRect = utils_1.doms.rect(this.containerEle); const contentRect = utils_1.doms.rect(this.contentEle); const clientHeight = containerRect.height; const clientWidth = containerRect.width; const scrollHeight = contentRect.height; const scrollWidth = contentRect.width; const verticalThumbBtnHeight = clientHeight / scrollHeight * clientHeight; const verticalThumbHeight = clientHeight - verticalThumbBtnHeight; const horizontalThumbBtnWidth = clientWidth / scrollWidth * clientWidth; const horizontalThumbWidth = clientWidth - horizontalThumbBtnWidth; this.setState({ clientHeight, clientWidth, scrollHeight, scrollWidth, verticalThumbBtnHeight, verticalThumbHeight, horizontalThumbBtnWidth, horizontalThumbWidth }); } clearThumbTimer() { if (this.thumbTimer !== null) { clearTimeout(this.thumbTimer); this.thumbTimer = null; } } clearScrollingTimer() { if (this.scrollingTimer !== null) { clearTimeout(this.scrollingTimer); this.scrollingTimer = null; } } componentDidMount() { utils_1.doms.on(document.body, 'mousemove', this.onDragingHandler); utils_1.doms.on(document.body, 'mouseup', this.onDragEndHandlr); utils_1.doms.on(this.contentEle, 'wheel', this.onMouseWheel); this.resizeObserver = new resize_observer_polyfill_1.default(this.getSize); this.resizeObserver.observe(this.containerEle); } componentWillUnmount() { utils_1.doms.off(document.body, 'mousemove', this.onDragingHandler); utils_1.doms.off(document.body, 'mouseup', this.onDragEndHandlr); utils_1.doms.off(this.contentEle, 'wheel', this.onMouseWheel); if (utils_1.isExist(this.animId)) { window.cancelAnimationFrame(this.animId); } this.clearThumbTimer(); this.clearScrollingTimer(); this.resizeObserver.unobserve(this.contentEle); this.resizeObserver.disconnect(); } render() { const { children, className, style } = this.props; const { clientHeight, scrollHeight, scrollWidth, clientWidth, left, top, verticalThumbBtnHeight, horizontalThumbBtnWidth, showThumb } = this.state; const componentClass = classnames_1.default({ [utils_1.preClass('scrollbar')]: true, [className]: utils_1.isExist(className) }); const contentStyle = { transform: `translate(-${left}px, -${top}px)` }; const verticalThumbStyle = { height: verticalThumbBtnHeight, transform: `translateY(${top * clientHeight / scrollHeight}px)` }; const horizontalThumbStyle = { width: horizontalThumbBtnWidth, transform: `translatex(${left * clientWidth / scrollWidth}px)` }; return (React.createElement("section", { className: componentClass, style: style, onMouseEnter: this.onMouseEnterHandler, onMouseLeave: this.onMouseLeaveHandler, ref: (ele) => { this.containerEle = ele; } }, React.createElement("div", { className: utils_1.preClass('scrollbar-content'), style: contentStyle, ref: (ele) => { this.contentEle = ele; } }, children), scrollHeight > clientHeight && React.createElement(transition_1.default, { visible: showThumb }, React.createElement("div", { className: utils_1.preClass('scrollbar-vertical-track') }, React.createElement("span", { style: verticalThumbStyle, onMouseDown: this.onDragStartHandler.bind(this, 'vertical'), className: utils_1.preClass('scrollbar-vertical-thumb') }))), scrollWidth > clientWidth && React.createElement(transition_1.default, { visible: showThumb }, React.createElement("div", { className: utils_1.preClass('scrollbar-horizontal-track') }, React.createElement("span", { style: horizontalThumbStyle, onMouseDown: this.onDragStartHandler.bind(this, 'horizontal'), className: utils_1.preClass('scrollbar-horizontal-thumb') }))))); } } Scrollbar.propTypes = { className: PropTypes.string, style: PropTypes.object, wheelSpeed: PropTypes.number, onScroll: PropTypes.func, onScrollStart: PropTypes.func, onScrollStop: PropTypes.func, autoHideThumb: PropTypes.bool, autoHideThumbDelay: PropTypes.number }; Scrollbar.defaultProps = { wheelSpeed: 50, autoHideThumb: true, autoHideThumbDelay: 1000 }; exports.default = Scrollbar;