import React, { Component } from 'react' import PropTypes from 'prop-types' import assign from 'object-assign' import _ from 'lodash' import CX from 'classnames' import './index.less' /** * ProgressBar * vertical 设置进度条是否垂直显示 * trackHover trackHover事件 * onSlide 事件函数获取percent值 * percent 设置滑块位置,0~100之间 * style 最外层div的样式 * slidedStyle 滑块左侧划过部分的样式 * trackStyle 滑块右侧未划过部分的样式 * ballStyle 滑块的样式 * showHoverStyle 是否设置hover时的样式 * hoverStyle 最外层div的样式 * hoverSlidedStyle 滑块左侧划过部分的样式 * hoverTrackStyle 滑块右侧未划过部分的样式 * hoverBallStyle 滑块的样式 * dragInfo 滑动滑块时显示在滑块上方的提示信息,默认没有提示信息 * dragInfoWrapStyle 滑块提示信息父级元素的样式,可用于调整提示信息的位置 * previewInfo 指针在进度条内时显示的指针位置进度提示信息 * previewInfoWrapStyle previewInfo 提示信息父级元素的样式,可用于调整提示信息的位置 * onCursorSlide 事件函数获取当前指针处的percent 可用于更新previewInfo */ class ProgressBar extends Component { static propTypes = { vertical: PropTypes.bool, onSlide: PropTypes.func, style: PropTypes.object, slidedStyle: PropTypes.object, trackStyle: PropTypes.object, ballStyle: PropTypes.object, showHoverStyle: PropTypes.bool, hoverStyle: PropTypes.object, hoverSlidedStyle: PropTypes.object, hoverTrackStyle: PropTypes.object, hoverBallStyle: PropTypes.object, percent: PropTypes.number, dragInfo: PropTypes.element, dragInfoWrapStyle: PropTypes.object, previewInfo: PropTypes.element, previewInfoWrapStyle: PropTypes.object, onCursorSlide: PropTypes.func, disableSlide: PropTypes.bool, } static defaultProps = { vertical: false, onSlide: _.noop, style: {}, slidedStyle: {}, trackStyle: {}, ballStyle: {}, showHoverStyle: false, hoverStyle: {}, hoverSlidedStyle: {}, hoverTrackStyle: {}, hoverBallStyle: {}, percent: 0, dragInfo: null, dragInfoWrapStyle: {}, previewInfo: null, previewInfoWrapStyle: {}, onCursorSlide: _.noop, disableSlide: false, } state = { percent: this.props.percent, cursorPercent: 0, moveFlag: false, cursorInSlideBall: false, cursorInComponent: false, } componentDidMount() { this.rangeSlideElem.addEventListener('mousedown', this.onWrapElemMouseDown) this.rangeSlideElem.addEventListener('mouseenter', this.onWrapElemMouseEnter) this.rangeSlideElem.addEventListener('mousemove', this.onWrapElemMouseMove) this.rangeSlideElem.addEventListener('mouseleave', this.onWrapElemMouseLeave) this.rangeSlideElem.addEventListener('click', this.handleSlide) document.body.addEventListener('mousemove', this.onBodyMouseMove) document.body.addEventListener('mouseup', this.onBodyMouseUp) document.body.addEventListener('mouseleave', this.onBodyMouseLeave) } componentWillReceiveProps(nextProps) { if (nextProps.percent !== this.props.percent) { this.setState({ percent: nextProps.percent, }) } } componentWillUnmount() { document.body.removeEventListener('mousemove', this.onBodyMouseMove) document.body.removeEventListener('mouseup', this.onBodyMouseUp) document.body.removeEventListener('mouseleave', this.onBodyMouseLeave) } getPercent = (e) => { let percentage if (this.props.vertical === false) { const offsetLeft = this.rangeSlideElem.getBoundingClientRect().x const { offsetWidth } = this.rangeSlideElem percentage = Math.round(((e.pageX - offsetLeft) / offsetWidth) * 100) } else { const offsetTop = this.rangeSlideElem.getBoundingClientRect().y const { offsetHeight } = this.rangeSlideElem percentage = Math.round((1 - (e.pageY - offsetTop) / offsetHeight) * 100) } percentage = Math.max(Math.min(percentage, 100), 0) return percentage } onWrapElemMouseDown = () => { this.setState({ moveFlag: true, }) } onBodyMouseMove = _.throttle((e) => { if (this.state.moveFlag) { this.handleSlide(e) } }, 30) onBodyMouseUp = () => { this.setState({ moveFlag: false, }) } onBodyMouseLeave = this.onBodyMouseUp handleSlide = (e) => { if (this.props.disableSlide === true) { return } const percent = this.getPercent(e) this.props.onSlide(percent) this.setState({ percent, }) } onSlideBallMouseEnter = () => { this.setState({ cursorInSlideBall: true, }) } onSlideBallMouseLeave = () => { this.setState({ cursorInSlideBall: false, }) } onWrapElemMouseEnter = (e) => { const cursorPercent = this.getPercent(e) this.props.onCursorSlide(cursorPercent) this.setState({ cursorInComponent: true, cursorPercent, }) } onWrapElemMouseMove = (e) => { const cursorPercent = this.getPercent(e) this.props.onCursorSlide(cursorPercent) this.setState({ cursorPercent, }) } onWrapElemMouseLeave = () => { this.setState({ cursorInComponent: false, }) } rangeSlideElem activeBarElem render() { const { cursorInComponent } = this.state const showHoverStyle = cursorInComponent && this.props.showHoverStyle const wrapStyles = assign({}, showHoverStyle ? this.props.hoverStyle : this.props.style) let slideTrackStyles if (this.props.vertical === true) { slideTrackStyles = assign({}, showHoverStyle ? this.props.hoverTrackStyle : this.props.trackStyle, { height: `${100 - this.state.percent}%`, }) } else { slideTrackStyles = assign({}, showHoverStyle ? this.props.hoverTrackStyle : this.props.trackStyle, { `${100 - this.state.percent}%`, }) } const activeBarStyles = assign({}, showHoverStyle ? this.props.hoverSlidedStyle : this.props.slidedStyle) const slideBallStyles = assign({}, showHoverStyle ? this.props.hoverBallStyle : this.props.ballStyle) const dragInfoWrapStyle = assign({}, this.props.dragInfoWrapStyle) const previewInfoWrapStyle = assign({}, this.props.previewInfoWrapStyle, { left: `${this.state.cursorPercent}%`, }) const showDragInfo = this.state.cursorInSlideBall || this.state.moveFlag const showPreviewInfo = showDragInfo === false && this.state.cursorInComponent return ( <div className={CX({ 'horizontal-progress-bar-component-wrap': this.props.vertical === false, 'vertical-progress-bar-component-wrap': this.props.vertical === true, })} style={wrapStyles} ref={(r) => { this.rangeSlideElem = r }} > <div className="active-bar" style={activeBarStyles} /> <div className="slide-track" ref={(r) => { this.activeBarElem = r }} style={slideTrackStyles} > <div className="slide-ball" style={slideBallStyles} onMouseEnter={this.onSlideBallMouseEnter} onMouseLeave={this.onSlideBallMouseLeave} /> { showDragInfo && ( <div className="drag-info-element-wrap" style={dragInfoWrapStyle} > {this.props.dragInfo} </div> ) } </div> { showPreviewInfo && ( <div className="preview-info-element-wrap" style={previewInfoWrapStyle} > {this.props.previewInfo} </div> ) } </div> ) } } export default ProgressBar
样式如下:
.horizontal-progress-bar-component-wrap { width: 100%; height: 12px; margin: 0; position:relative; cursor: pointer; .active-bar { position:absolute; top: 50%; left: 0; margin-top: -2px; width: 100%; height: 4px; border-radius: 4px; background: linear-gradient(to right, #81d5fa, #3977f6); } .slide-track { width: 50%; height: 4px; position:absolute; top: 50%; right: 0; margin-top: -2px; border-radius: 4px; background: #fff; .slide-ball { width: 12px; height: 12px; position: absolute; left: -6px; top: -4px; border-radius: 50%; cursor: pointer; background: url('~ROOT/shared/assets/image/vn-circle-blue-42-42.png') no-repeat center; background-size: 12px; } .drag-info-element-wrap { position: absolute; left: -24px; top: -46px; } } .preview-info-element-wrap { position: absolute; top: -32px; margin-left: -24px; } } .vertical-progress-bar-component-wrap { width: 12px; height: 100%; margin: 0; position: relative; cursor: pointer; .active-bar { position:absolute; bottom: 0; left: 50%; margin-left: -2px; height: 100%; width: 4px; border-radius: 4px; background: linear-gradient(to top, #81d5fa, #3977f6); } .slide-track { position:absolute; height: 50%; width: 4px; right: 50%; top: -2px; margin-right: -2px; border-radius: 4px; background: #fff; .slide-ball { width: 12px; height: 12px; position: absolute; left: -4px; bottom: -6px; border-radius: 50%; cursor: pointer; background: url('~ROOT/shared/assets/image/vn-circle-blue-42-42.png') no-repeat center; background-size: 12px; } .drag-info-element-wrap { position: absolute; left: -46px; bottom: -24px; } } .preview-info-element-wrap { position: absolute; left: -32px; margin-bottom: -24px; } }