chowa
Version:
UI component library based on React
342 lines (341 loc) • 12.4 kB
JavaScript
/**
* @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;