音量调节条-封装通用的ProgressBar组件

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, {
        width: `${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;
  }
}

 

posted @ 2019-09-05 13:57  贝子涵夕  阅读(698)  评论(0编辑  收藏  举报