UNPKG

@evercoder/react-circular-slider-bar

Version:

A circular slider component for react

162 lines (139 loc) 4.38 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Arc from './Arc'; import Track from './Track'; import Thumb from './Thumb'; import { pipe, toRad, toDeg, getRelativeAngle } from './utils'; class App extends Component { static propTypes = { r: PropTypes.number, initialAngle: PropTypes.number, value: PropTypes.number, trackWidth: PropTypes.number, trackColor: PropTypes.string, arcColor: PropTypes.string, thumbWidth: PropTypes.number, thumbColor: PropTypes.string, thumbBorderWidth: PropTypes.number, thumbBorderColor: PropTypes.string, onChange: PropTypes.func } static defaultProps = { r: 80, initialAngle: 90, value: undefined, trackWidth: 2, trackColor: '#f5f5dc', arcColor: '#7985f1', thumbWidth: 10, thumbColor: 'white', thumbBorderWidth: 2, thumbBorderColor: '#cccccc', onChange: value => {} } constructor(props) { super(props) document.addEventListener('touchend', this.thumbLeave); document.addEventListener('mouseup', this.thumbLeave); } componentDidMount = () => { this.offsets = this.ref.current.getBoundingClientRect() } angle = () => this.props.value !== undefined ? getRelativeAngle((this.props.value / 100) * 360, this.props.initialAngle) : this.state.angle; thumbSelect = () => { document.addEventListener('touchmove', this.moveThumb) document.addEventListener('mousemove', this.moveThumb) } thumbLeave = () => { document.removeEventListener('touchmove', this.moveThumb) document.removeEventListener('mousemove', this.moveThumb) } moveThumb = evt => { const event = evt.changedTouches ? evt.changedTouches[0] : evt; const angle = pipe( this.calculateAngle(event.clientX, event.clientY), this.limitAngleVariation ) if(!this.props.value) this.setState({angle}) this.handleChange(angle) } calculateAngle = (mouseX, mouseY) => { const x = mouseX - this.props.r - this.offsets.left; const y = - mouseY + this.props.r + this.offsets.top; const angle = toDeg(Math.atan(y / x)) + ((x < 0) ? 180 : 0) + ((x >= 0 && y < 0) ? 360 : 0); return angle; } limitAngleVariation = (angle) => { const nextRelativeAngle = getRelativeAngle(angle, this.props.initialAngle); const currentRelativeAngle = getRelativeAngle(this.angle(), this.props.initialAngle); if (currentRelativeAngle < 10 && Math.abs(nextRelativeAngle - currentRelativeAngle) > 180) return this.props.initialAngle; if (currentRelativeAngle > 350 && Math.abs(nextRelativeAngle - currentRelativeAngle) > 180) return (this.props.initialAngle + 361) % 360; return ( (nextRelativeAngle < currentRelativeAngle + this.limitAngleFactor) && (nextRelativeAngle > currentRelativeAngle - this.limitAngleFactor) ) ? angle : this.angle(); } calculateThumbPosition = (angle) => { const {r, trackWidth} = this.props; const x = Math.cos(toRad(angle)) * (r + (trackWidth / 2)) + r + trackWidth const y = - Math.sin(toRad(angle)) * (r + (trackWidth / 2)) + r + trackWidth return {x, y} } handleChange = (angle) => { const percent = (getRelativeAngle(angle, this.props.initialAngle) / 360) * 100 this.props.onChange(percent); } limitAngleFactor = 90; ref = React.createRef(); state = { angle: undefined } render() { return ( <div id="circular-slider" style={{ width: this.props.r * 2, height: this.props.r * 2, position: 'relative' }} ref={this.ref} > <Track width={this.props.trackWidth} color={this.props.trackColor} /> <Arc r={this.props.r} angle={this.angle()} initialAngle={this.props.initialAngle} width={this.props.trackWidth} color={this.props.arcColor} /> <Thumb diameter={this.props.thumbWidth} color={this.props.thumbColor} borderWidth={this.props.thumbBorderWidth} borderColor={this.props.thumbBorderColor} position={this.calculateThumbPosition(this.angle())} handleSelect={this.thumbSelect} /> </div> ); } } export default App;