Skip to content

Commit

Permalink
feat(range): 新增 range 组件
Browse files Browse the repository at this point in the history
  • Loading branch information
jimczj committed Nov 20, 2018
1 parent 56adbc1 commit 68885d7
Show file tree
Hide file tree
Showing 7 changed files with 389 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ class App extends Component {
'pages/form/picker-view/index',
'pages/form/slider/index',
'pages/form/search-bar/index',
'pages/form/image-picker/index'
'pages/form/image-picker/index',
'pages/form/range/index'
],
window: {
backgroundTextStyle: 'light',
Expand Down
241 changes: 241 additions & 0 deletions src/components/range/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import Taro from '@tarojs/taro'
import PropTypes from 'prop-types'
import { View } from '@tarojs/components'
import classNames from 'classnames'

import AtComponent from '../../common/component'
import './index.scss'

const defaultFunc = () => {}

export default class AtRange extends AtComponent {
static defaultProps = {
customStyle: '',
className: '',
sliderStyle: '',
railStyle: '',
trackStyle: '',
value: [0, 0],
min: 0,
max: 100,
disabled: false,
onChange: defaultFunc,
onAfterChange: defaultFunc
}

static propTypes = {
customStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
className: PropTypes.oneOfType([
PropTypes.array,
PropTypes.string
]),
sliderStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
railStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
trackStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
value: PropTypes.array,
min: PropTypes.number,
max: PropTypes.number,
disabled: PropTypes.bool,
onChange: PropTypes.func
}

constructor (props) {
super(...arguments)
const { max, min } = props
// range 宽度
this.width = 0
// range 到屏幕左边的距离
this.left = 0
this.deltaValue = max - min
this.currentSlider = ''
this.state = {
slider1X: 0,
slider2X: 0
}
}

handleClick (event) {
if (this.currentSlider && !this.props.disabled) {
const env = Taro.getEnv()
let sliderValue = 0
if (env === Taro.ENV_TYPE.WEB) {
sliderValue = event.pageX - this.left
} else if (env === Taro.ENV_TYPE.WEAPP) {
sliderValue = event.target.x - this.left
}
this.setSliderValue(this.currentSlider, sliderValue, 'onChange')
}
}

handleTouchMove (sliderName, event) {
if (this.props.disabled) return

const clientX = event.touches[0].clientX
this.setSliderValue(sliderName, clientX - this.left, 'onChange')
}

handleTouchEnd (sliderName) {
if (this.props.disabled) return

this.currentSlider = sliderName
this.triggerEvent('onAfterChange')
}

setSliderValue (sliderName, targetValue, funcName) {
let value = 0
if (targetValue < 0) {
value = 0
} else if (targetValue > this.width) {
value = this.width
} else {
value = targetValue
}

value = Math.floor((value / this.width) * 100)
if (funcName) {
this.setState({
[sliderName]: value
}, () => (this.triggerEvent(funcName)))
} else {
this.setState({
[sliderName]: value
})
}
}

setValue (value) {
const slider1X = Math.round((value[0] / this.deltaValue) * 100)
const slider2X = Math.round((value[1] / this.deltaValue) * 100)
this.setState({
slider1X,
slider2X
})
}

triggerEvent (funcName) {
const { slider1X, slider2X } = this.state
const value1 = Math.round((slider1X / 100) * this.deltaValue)
const value2 = Math.round((slider2X / 100) * this.deltaValue)
const valueArr = []
if (value1 > value2) {
valueArr.push(value2)
valueArr.push(value1)
} else {
valueArr.push(value1)
valueArr.push(value2)
}

if (funcName === 'onChange') {
this.props.onChange(valueArr)
} else if (funcName === 'onAfterChange') {
this.props.onAfterChange(valueArr)
}
}

componentWillReceiveProps (nextProps) {
const { value } = nextProps
if (this.props.value[0] !== value[0] && this.props.value[1] !== value[1]) {
this.setValue(value)
}
}

componentDidMount () {
// 获取 range 宽度
const env = Taro.getEnv()
const { value } = this.props
// 异步解决获取 offsetWidth 不准确问题
setTimeout(() => {
if (env === Taro.ENV_TYPE.WEB) {
this.width = Math.round(this.rangeRef.vnode.dom.offsetWidth)
this.left = Math.round(this.rangeRef.vnode.dom.offsetLeft)
this.setValue(value)
} else if (env === Taro.ENV_TYPE.WEAPP) {
this.rangeRef.boundingClientRect(rect => {
this.width = Math.round(rect.width)
this.left = Math.round(rect.left)
this.setValue(value)
}).exec()
}
})
}

getRangeRef = node => (this.rangeRef = node)

render () {
const {
className,
customStyle,
sliderStyle,
railStyle,
trackStyle,
disabled
} = this.props

const { slider1X, slider2X } = this.state
const slider1Style = {
left: `${slider1X}%`
}
const slider2Style = {
left: `${slider2X}%`
}
const smallX = slider1X > slider2X ? slider2X : slider1X
const deltaX = Math.abs(slider1X - slider2X)
const atTrackStyle = {
left: `${smallX}%`,
width: `${deltaX}%`
}

return (
<View
className={
classNames({
'at-range': true,
'at-range--disabled': disabled
}, className)}
style={customStyle}
onClick={this.handleClick.bind(this)}
>
<View
className='at-range__container'
ref={this.getRangeRef}
>
<View
className='at-range__rail'
style={railStyle}
>
</View>
<View
className='at-range__track'
style={this.mergeStyle(atTrackStyle, trackStyle)}
></View>
<View
style={this.mergeStyle(slider1Style, sliderStyle)}
className='at-range__slider'
onTouchMove={this.handleTouchMove.bind(this, 'slider1X')}
onTouchEnd={this.handleTouchEnd.bind(this, 'slider1X')}
>
</View>
<View
style={this.mergeStyle(slider2Style, sliderStyle)}
className='at-range__slider'
onTouchMove={this.handleTouchMove.bind(this, 'slider2X')}
onTouchEnd={this.handleTouchEnd.bind(this, 'slider2X')}
>
</View>
</View>
</View>
)
}
}
54 changes: 54 additions & 0 deletions src/components/range/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@import "../../style/theme/default.scss";
@import "../../style/mixins/index.scss";

$height: 22PX;

.at-range {
position: relative;
width: 100%;
padding: 0 $height;
box-sizing: border-box;

&__container {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: $height;
box-sizing: content-box;
}

&__rail {
position: absolute;
z-index: $zindex-form - 1;
width: 100%;
height: 2PX;
background-color: $color-grey-3;
box-sizing: border-box;
overflow: hidden;
}

&__track {
position: absolute;
width: 20%;
height: 2PX;
background-color: $color-brand;
z-index: $zindex-form;
}

&__slider {
position: absolute;
z-index: $zindex-form + 1;
width: $height;
height: $height;
border-radius: 50%;
border: 1px solid $color-border-grey;
box-shadow: 0 4px 4px 0 $color-border-grey;
background-color: #fff;
box-sizing: border-box;
}

&--disabled {
opacity: $opacity-disabled;
}
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export { default as AtSteps } from './components/steps'
export { default as AtCurtain } from './components/curtain'
export { AtMessage, message } from './components/message'
export { default as AtImagePicker } from './components/image-picker'
export { default as AtRange } from './components/range'

/* 私有的组件 */
export { default as AtLoading } from './components/loading'
Expand Down
83 changes: 83 additions & 0 deletions src/pages/form/range/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Taro from '@tarojs/taro'
import { View } from '@tarojs/components'
import AtRange from '../../../components/range/index'
import AtToast from '../../../components/toast/index'
import DocsHeader from '../../components/doc-header'
import './index.scss'

export default class Index extends Taro.Component {
config = {
navigationBarTitleText: 'Taro UI'
}

constructor () {
super(...arguments)
this.state = {
isOpened: false,
text: '',
}
}

handleChange (value) {
this.setState({
isOpened: true,
text: `${value[0]}~${value[1]}`
})
}

handleClose () {
this.setState({
isOpened: false
})
}
render () {
return (
<View className='page'>
<AtToast
text={this.state.text}
isOpened={this.state.isOpened}
onClose={this.handleClose.bind(this)}
/>
{/* S Header */}
<DocsHeader title='Range 范围选择器'></DocsHeader>
{/* E Header */}

{/* S Body */}
<View className='doc-body'>
{/* 基础用法 */}
<View className='panel'>
<View className='panel__title'>基础用法</View>
<View className='panel__content'>
<AtRange
value={[20, 60]}
onAfterChange={this.handleChange.bind(this)}
onChange={this.handleChange.bind(this)}
/>
</View>
</View>
{/* 自定义样式 */}
<View className='panel'>
<View className='panel__title'>自定义样式</View>
<View className='panel__content'>
<AtRange
sliderStyle={{ backgroundColor: '#6190E8' }}
value={[20, 40]}
/>
</View>
</View>
{/* 禁止状态 */}
<View className='panel'>
<View className='panel__title'>禁止状态</View>
<View className='panel__content'>
<AtRange
value={[20, 40]}
disabled
/>
</View>
</View>
</View>
{/* E Body */}
</View>
)
}
}
Loading

0 comments on commit 68885d7

Please sign in to comment.