[Improvement] Slider: add step & bar-height prop (#915)

This commit is contained in:
neverland 2018-04-23 20:19:41 +08:00 committed by GitHub
parent 19f4c9c028
commit b93a2e3c00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 350 deletions

View File

@ -1,19 +1,15 @@
<template> <template>
<demo-section> <demo-section>
<demo-block :title="$t('title1')"> <demo-block :title="$t('title1')">
<van-slider v-model="value1"/> <van-slider v-model="value1" @change="onChange" />
<van-row>
<van-col span="24">
<van-stepper v-model="value1" />
</van-col>
</van-row>
</demo-block> </demo-block>
<demo-block :title="$t('title2')"> <demo-block :title="$t('title2')">
<van-slider <van-slider
v-model="value2" v-model="value2"
:min="min" :min="10"
:max="max" :max="90"
@change="onChange"
/> />
</demo-block> </demo-block>
@ -24,17 +20,9 @@
<demo-block :title="$t('title4')"> <demo-block :title="$t('title4')">
<van-slider <van-slider
v-model="value4" v-model="value4"
@change="handleChange" :step="10"
@after-change="handleAfterChange" bar-height="4px"
/> @change="onChange"
</demo-block>
<demo-block :title="$t('title5')">
<van-slider
v-model="value5"
pivot-color="#333"
loaded-bar-color="red"
bar-color="blue"
/> />
</demo-block> </demo-block>
</demo-section> </demo-section>
@ -45,46 +33,43 @@ export default {
i18n: { i18n: {
'zh-CN': { 'zh-CN': {
title1: '基本用法', title1: '基本用法',
title2: '最大最小值', title2: '指定选择范围',
title3: '禁用', title3: '禁用',
title4: '事件', title4: '指定步长',
title5: '自定义样式' text: '当前值:'
}, },
'en-US': { 'en-US': {
title1: 'Basic Usage', title1: 'Basic Usage',
title2: 'Max && Min', title2: 'Range',
title3: 'Disabed', title3: 'Disabled',
title4: 'Event', title4: 'Step size',
title5: 'Customized style' text: 'Current value: '
} }
}, },
data() { data() {
return { return {
value1: 50, value1: 50,
value2: 50, value2: 50,
value3: 50, value3: 50,
value4: 50, value4: 50
value5: 50, };
min: 10,
max: 90
}
}, },
methods: { methods: {
handleChange(value) { onChange(value) {
console.log('handleChange:', value) this.$toast(this.$t('text') + value);
},
handleAfterChange(value) {
console.log('handleAfterChange:', value)
} }
} }
}; };
</script> </script>
<style lang="postcss"> <style lang="postcss">
.van-row { .demo-slider {
padding-top: 20px; user-select: none;
.van-col {
text-align: center; .van-slider {
} margin: 0 15px 30px;
} }
}
</style> </style>

View File

@ -11,99 +11,56 @@ Vue.use(Slider);
#### Basic Usage #### Basic Usage
```html ```html
<van-slider v-model="value1"/> <van-slider v-model="value" @change="onChange" />
<van-row>
<van-col span="12">
<van-stepper v-model="value1" />
</van-col>
</van-row>
``` ```
```js ```js
data() { export default {
return { data() {
value1: 50 return {
} value: 50
} };
```
#### Max && Min
```html
<van-slider
v-model="value2"
:min="min"
:max="max"
/>
```
```js
data() {
return {
value2: 50,
min: 10,
max: 90
}
}
```
#### Disabed
```html
<van-slider v-model="value3" disabled />
```
#### Customized style
```html
<van-slider
v-model="value4"
@change="handleChange"
@after-change="handleAfterChange"
/>
```
```js
data() {
return {
value4: 50
}
},
methods: {
handleChange(value) {
console.log('handleChange:', value)
}, },
handleAfterChange(value) {
console.log('handleAfterChange:', value) methods: {
onChange(value) {
this.$toast('Current value' + value);
}
} }
} };
``` ```
### Customized style #### Range
```html ```html
<van-slider <van-slider v-model="value" :min="10" :max="90" />
v-model="value5" ```
pivot-color="#333"
loaded-bar-color="red" #### Disabled
bar-color="blue"
/> ```html
<van-slider v-model="value" disabled />
```
#### Step size
```html
<van-slider v-model="value" :step="10" bar-height="4px" />
``` ```
### API ### API
| Attribute | Description | Type | Default | Accepted Values | | Attribute | Description | Type | Default |
|-----------|-----------|-----------|-------------|-------------| |-----------|-----------|-----------|-------------|-------------|
| value | current value | Number | 0 | - | | value | Current value | `Number` | `0` |
| disabled | disabled | Boolean | false | - | | disabled | Whether to disable slider | `Boolean` | `false` |
| bar-color | bar-color | string | `#cacaca` | - | | max | Max value | `Number` | `100` |
| loaded-bar-color | loaded-bar-color | string | `#4b0` | - | | min | Min value | `Number` | `0` |
| pivot-color | pivot-color | string | `#4b0` | - | | step | Step size | `Number` | `1` |
| max | max | Number | 100 | - | | bar-height | Height of bar | `String` | `2px` |
| min | min | Number | 0 | - |
### Event ### Event
| Event | Description | Arguments | | Event | Description | Arguments |
|-----------|-----------|-----------| |-----------|-----------|-----------|
| change | touchmove emit | value | | change | Triggered after value change | value: current rate |
| after-change | touchend emit | value |

View File

@ -10,99 +10,56 @@ Vue.use(Slider);
#### 基本用法 #### 基本用法
```html ```html
<van-slider v-model="value1"/> <van-slider v-model="value" @change="onChange" />
<van-row>
<van-col span="12">
<van-stepper v-model="value1" />
</van-col>
</van-row>
``` ```
```js ```js
data() { export default {
return { data() {
value1: 50 return {
value: 50
};
},
methods: {
onChange(value) {
this.$toast('当前值:' + value);
}
} }
} };
``` ```
#### 最大最小值 #### 指定选择范围
```html ```html
<van-slider <van-slider v-model="value" :min="10" :max="90" />
v-model="value2"
:min="min"
:max="max"
/>
```
```js
data() {
return {
value2: 50,
min: 10,
max: 90
}
}
``` ```
#### 禁用 #### 禁用
```html ```html
<van-slider v-model="value3" disabled /> <van-slider v-model="value" disabled />
``` ```
#### 事件 #### 指定步长
```html ```html
<van-slider <van-slider v-model="value" :step="10" bar-height="4px" />
v-model="value4"
@change="handleChange"
@after-change="handleAfterChange"
/>
```
```js
data() {
return {
value4: 50
}
},
methods: {
handleChange(value) {
console.log('handleChange:', value)
},
handleAfterChange(value) {
console.log('handleAfterChange:', value)
}
}
```
### 自定义样式
```html
<van-slider
v-model="value5"
pivot-color="#333"
loaded-bar-color="red"
bar-color="blue"
/>
``` ```
### API ### API
| 参数 | 说明 | 类型 | 默认值 | 必须 | | 参数 | 说明 | 类型 | 默认值 |
|-----------|-----------|-----------|-------------|-------------| |-----------|-----------|-----------|-------------|
| value | 当前进度百分比 | Number | 0 | 否 | | value | 当前进度百分比 | `Number` | `0` |
| disabled | 滑块是否禁用 | Boolean | false | 否 | | disabled | 是否禁用滑块 | `Boolean` | `false` |
| bar-color | 进度条颜色 | string | `#cacaca` | 否 | | max | 最大值 | `Number` | `100` |
| loaded-bar-color | 已加载条颜色 | string | `#4b0` | 否 | | min | 最小值 | `Number` | `0` |
| pivot-color | 滑块颜色 | string | `#4b0` | 否 | | step | 步长 | `Number` | `1` |
| max | 最大值 | Number | 100 | - | | bar-height | 进度条高度 | `String` | `2px` |
| min | 最小值 | Number | 0 | - |
### Event ### Event
| 事件名 | 说明 | 参数 | | 事件名 | 说明 | 参数 |
|-----------|-----------|-----------| |-----------|-----------|-----------|
| change | 拖动时触发 | value | | change | 进度值改变后触发 | value: 当前进度 |
| after-change | 拖动停止后触发 | value |

View File

@ -136,6 +136,10 @@ module.exports = {
path: '/progress', path: '/progress',
title: 'Progress - 进度条' title: 'Progress - 进度条'
}, },
{
path: '/slider',
title: 'Slider - 滑块'
},
{ {
path: '/stepper', path: '/stepper',
title: 'Stepper - 步进器' title: 'Stepper - 步进器'
@ -148,10 +152,6 @@ module.exports = {
path: '/swipe', path: '/swipe',
title: 'Swipe - 轮播' title: 'Swipe - 轮播'
}, },
{
path: '/slider',
title: 'Slider - 滑块'
},
{ {
path: '/tab', path: '/tab',
title: 'Tab - 标签页' title: 'Tab - 标签页'
@ -434,6 +434,10 @@ module.exports = {
path: '/progress', path: '/progress',
title: 'Progress' title: 'Progress'
}, },
{
path: '/slider',
title: 'Slider'
},
{ {
path: '/stepper', path: '/stepper',
title: 'Stepper' title: 'Stepper'
@ -446,10 +450,6 @@ module.exports = {
path: '/swipe', path: '/swipe',
title: 'Swipe' title: 'Swipe'
}, },
{
path: '/slider',
title: 'Slider'
},
{ {
path: '/tab', path: '/tab',
title: 'Tab' title: 'Tab'

View File

@ -1,22 +1,8 @@
<template> <template>
<div <div :class="b({ disabled })" @click.stop="onClick">
class="van-slider" <div :class="b('bar')" :style="barStyle">
:class="{ 'van-slider--disabled': disabled }"
>
<div
class="van-slider__bar"
ref="bar"
:style="barStyle"
@click.stop="onSliderClicked"
>
<span <span
class="van-slider__finished-portion" :class="b('button')"
:style="finishedStyle"
/>
<span
class="van-slider__pivot"
ref="pivot"
:style="pivotStyle"
@touchstart="onTouchStart" @touchstart="onTouchStart"
@touchmove.prevent.stop="onTouchMove" @touchmove.prevent.stop="onTouchMove"
@touchend="onTouchEnd" @touchend="onTouchEnd"
@ -25,145 +11,90 @@
</div> </div>
</div> </div>
</template> </template>
<script>
<script>
import create from '../utils/create'; import create from '../utils/create';
import Touch from '../mixins/touch'; import Touch from '../mixins/touch';
const DEFAULT_COLOR = '#4b0';
const DEFAULT_BG = '#cacaca';
export default create({ export default create({
name: 'slider', name: 'slider',
mixins: [Touch], mixins: [Touch],
props: { props: {
disabled: Boolean,
max: { max: {
type: Number, type: Number,
default: 100 default: 100
}, },
min: { min: {
type: Number, type: Number,
default: 0 default: 0
}, },
step: {
type: Number,
default: 1
},
value: { value: {
type: Number, type: Number,
default: 0, default: 0
required: true
}, },
barHeight: {
disabled: Boolean,
pivotColor: {
type: String, type: String,
default: DEFAULT_COLOR default: '2px'
},
barColor: {
type: String,
default: DEFAULT_BG
},
loadedBarColor: {
type: String,
default: DEFAULT_COLOR
} }
}, },
data() {
return {
pivotOffset: 0
};
},
computed: { computed: {
sliderWidth() {
const rect = this.$refs.bar.getBoundingClientRect();
return rect['width'];
},
barStyle() { barStyle() {
return { return {
backgroundColor: this.barColor width: this.format(this.value) + '%',
}; height: this.barHeight
},
finishedStyle() {
return {
backgroundColor: this.loadedBarColor,
width: this.format(this.value) + '%'
};
},
pivotStyle() {
return {
backgroundColor: this.pivotColor,
left: this.format(this.value) + '%',
marginLeft: `-${this.pivotOffset}px`
}; };
} }
}, },
mounted() {
this.pivotOffset = parseInt(this.$refs.pivot.getBoundingClientRect().width / 2);
},
methods: { methods: {
onTouchStart(event) { onTouchStart(event) {
if (this.disabled) return; if (this.disabled) return;
this.draging = true;
this.touchStart(event); this.touchStart(event);
this.startValue = this.format(this.value); this.startValue = this.format(this.value);
}, },
onTouchMove(event) { onTouchMove(event) {
if (this.draging) { if (this.disabled) return;
this.touchMove(event);
const diff = this.deltaX / this.sliderWidth * 100; this.touchMove(event);
this.newValue = this.startValue + diff; const rect = this.$el.getBoundingClientRect();
this.updateValue(this.newValue); const diff = this.deltaX / rect.width * 100;
} this.updateValue(this.startValue + diff);
}, },
onTouchEnd() { onTouchEnd() {
if (this.draging) { if (this.disabled) return;
this.$nextTick(() => { this.updateValue(this.value, true);
this.draging = false;
this.updateValue(this.newValue, true);
});
}
}, },
onSliderClicked(e) { onClick(event) {
if (this.disabled || this.draging) return;
const sliderRect = this.$refs.bar.getBoundingClientRect();
const sliderOffset = sliderRect.left;
this.newValue = Math.round((e.clientX - sliderOffset) / this.sliderWidth * 100);
this.updateValue(this.newValue, true);
},
updateValue(value, isFinished) {
if (this.disabled) return; if (this.disabled) return;
const rect = this.$el.getBoundingClientRect();
const value = (event.clientX - rect.left) / rect.width * 100;
this.updateValue(value, true);
},
updateValue(value, end) {
value = this.format(value); value = this.format(value);
this.$emit('change', value);
this.$emit('input', value); this.$emit('input', value);
if (isFinished) { if (end) {
this.$emit('after-change', value); this.$emit('change', value);
} }
}, },
format(value) { format(value) {
return Math.round(this.range(value)); return (Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step);
},
range(value) {
return Math.max(this.min, Math.min(value, this.max));
} }
} }
}); });

View File

@ -1,41 +1,29 @@
@import './common/var.css'; @import './common/var.css';
$c-slider-background: #cacaca;
$c-pivot-border: #fff;
.van-slider { .van-slider {
&--disabled { position: relative;
opacity: .3; border-radius: 999px;
.van-slider__pivot { background-color: $border-color;
cursor: not-allowed;
}
}
&__bar { &__bar {
position: relative; position: relative;
margin: 0 15px; border-radius: inherit;
height: 4px; background-color: $blue;
border-radius: 5px;
background: $c-slider-background;
} }
&__loaded-portion, &__button {
&__finished-portion {
border-radius: 5px;
height: 100%;
position: absolute;
left: 0;
}
&__pivot {
position: absolute; position: absolute;
top: 50%; top: 50%;
width: 16px; right: 0;
height: 16px; width: 20px;
border: 3px solid $c-pivot-border; height: 20px;
box-shadow: 0 1px 4px;
border-radius: 50%; border-radius: 50%;
background-color: $c-slider-background; background-color: $white;
transform: translate3d(0, -50%, 0); transform: translate3d(50%, -50%, 0);
box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}
&--disabled {
opacity: .3;
} }
} }

View File

@ -19,7 +19,6 @@ describe('Slider', () => {
expect(wrapper.hasClass('van-slider')).to.be.true; expect(wrapper.hasClass('van-slider')).to.be.true;
expect(wrapper.find('.van-slider__bar').length).to.equal(1); expect(wrapper.find('.van-slider__bar').length).to.equal(1);
expect(wrapper.find('.van-slider__finished-portion').length).to.equal(1);
expect(wrapper.vm.value).to.equal(50); expect(wrapper.vm.value).to.equal(50);
expect(wrapper.hasClass('van-slider--disabled')).to.equal(true); expect(wrapper.hasClass('van-slider--disabled')).to.equal(true);
@ -40,8 +39,17 @@ describe('Slider', () => {
}); });
const eventStub = sinon.stub(wrapper.vm, '$emit'); const eventStub = sinon.stub(wrapper.vm, '$emit');
const $bar = wrapper.find('.van-slider__bar')[0]; const $bar = wrapper.find('.van-slider')[0];
$bar.trigger('click'); $bar.trigger('click');
const button = wrapper.find('.van-slider__button')[0];
triggerTouch(button, 'touchstart', 0, 0);
expect(wrapper.vm.startX).to.equal(undefined);
triggerTouch(button, 'touchmove', 50, 0);
expect(wrapper.vm.offsetX).to.equal(undefined);
triggerTouch(button, 'touchend', 50, 0);
expect(wrapper.vm.offsetX).to.equal(undefined);
expect(eventStub.called).to.equal(false); expect(eventStub.called).to.equal(false);
@ -55,44 +63,28 @@ describe('Slider', () => {
expect(eventStub.called).to.equal(true); expect(eventStub.called).to.equal(true);
}); });
it('Customized style', () => { it('drag button', () => {
const COLOR = '#123';
wrapper = mount(Slider, {
propsData: {
pivotColor: COLOR,
barColor: COLOR,
loadedBarColor: COLOR,
value: 50
}
});
expect(wrapper.vm.barStyle.backgroundColor).to.equal(COLOR);
expect(wrapper.vm.pivotStyle.backgroundColor).to.equal(COLOR);
expect(wrapper.vm.finishedStyle.backgroundColor).to.equal(COLOR);
});
it('drag pivot', () => {
wrapper = mount(Slider, { wrapper = mount(Slider, {
propsData: { propsData: {
value: 50 value: 50
} }
}); });
const pivot = wrapper.find('.van-slider__pivot')[0]; const button = wrapper.find('.van-slider__button')[0];
triggerTouch(pivot, 'touchstart', 0, 0); triggerTouch(button, 'touchstart', 0, 0);
expect(wrapper.vm.startX).to.equal(0); expect(wrapper.vm.startX).to.equal(0);
triggerTouch(pivot, 'touchmove', 50, 0); triggerTouch(button, 'touchmove', 50, 0);
expect(wrapper.vm.offsetX).to.equal(50); expect(wrapper.vm.offsetX).to.equal(50);
triggerTouch(pivot, 'touchend', 50, 0); triggerTouch(button, 'touchend', 50, 0);
expect(wrapper.vm.offsetX).to.equal(50); expect(wrapper.vm.offsetX).to.equal(50);
wrapper.setData({ wrapper.setData({
disabled: true disabled: true
}); });
triggerTouch(pivot, 'touchstart', 0, 0); triggerTouch(button, 'touchstart', 0, 0);
expect(wrapper.vm.startX).to.equal(0); expect(wrapper.vm.startX).to.equal(0);
}); });
}); });