feat(2.x/slider): add dual thumb mode for slider (#7176)

* feat(slider): add dual thumb mode for slider

* fix: test error
This commit is contained in:
被雨水过滤的空气
2020-09-14 06:49:30 +08:00
committed by GitHub
parent 2c38469591
commit 9b32a13bef
6 changed files with 315 additions and 58 deletions
+120 -28
View File
@@ -1,16 +1,22 @@
import { createNamespace, addUnit } from '../utils';
import { deepClone } from '../utils/deep-clone';
import { preventDefault } from '../utils/dom/event';
import { TouchMixin } from '../mixins/touch';
import { FieldMixin } from '../mixins/field';
const [createComponent, bem] = createNamespace('slider');
const isSameValue = (newValue, oldValue) => {
return JSON.stringify(newValue) === JSON.stringify(oldValue);
};
export default createComponent({
mixins: [TouchMixin, FieldMixin],
props: {
disabled: Boolean,
vertical: Boolean,
range: Boolean,
barHeight: [Number, String],
buttonSize: [Number, String],
activeColor: String,
@@ -28,7 +34,7 @@ export default createComponent({
default: 1,
},
value: {
type: Number,
type: [Number, Array],
default: 0,
},
},
@@ -40,7 +46,7 @@ export default createComponent({
},
computed: {
range() {
scope() {
return this.max - this.min;
},
@@ -61,7 +67,12 @@ export default createComponent({
},
mounted() {
this.bindTouchEvent(this.$refs.wrapper);
if (this.range) {
this.bindTouchEvent(this.$refs.wrapper0);
this.bindTouchEvent(this.$refs.wrapper1);
} else {
this.bindTouchEvent(this.$refs.wrapper);
}
},
methods: {
@@ -71,7 +82,12 @@ export default createComponent({
}
this.touchStart(event);
this.startValue = this.format(this.value);
this.currentValue = this.value;
if (this.range) {
this.startValue = this.value.map(this.format);
} else {
this.startValue = this.format(this.value);
}
this.dragStatus = 'start';
},
@@ -91,10 +107,14 @@ export default createComponent({
const rect = this.$el.getBoundingClientRect();
const delta = this.vertical ? this.deltaY : this.deltaX;
const total = this.vertical ? rect.height : rect.width;
const diff = (delta / total) * this.range;
const diff = (delta / total) * this.scope;
this.newValue = this.startValue + diff;
this.updateValue(this.newValue);
if (this.range) {
this.currentValue[this.index] = this.startValue[this.index] + diff;
} else {
this.currentValue = this.startValue + diff;
}
this.updateValue(this.currentValue);
},
onTouchEnd() {
@@ -103,7 +123,7 @@ export default createComponent({
}
if (this.dragStatus === 'draging') {
this.updateValue(this.newValue, true);
this.updateValue(this.currentValue, true);
this.$emit('drag-end');
}
@@ -120,20 +140,44 @@ export default createComponent({
? event.clientY - rect.top
: event.clientX - rect.left;
const total = this.vertical ? rect.height : rect.width;
const value = +this.min + (delta / total) * this.range;
let value = +this.min + (delta / total) * this.scope;
if (this.range) {
let [left, right] = this.value;
const middle = (left + right) / 2;
if (value <= middle) {
left = value;
} else {
right = value;
}
value = [left, right];
}
this.startValue = this.value;
this.updateValue(value, true);
},
updateValue(value, end) {
value = this.format(value);
// 处理两个滑块重叠之后的情况
handleOverlap(value) {
if (value[0] > value[1]) {
value = deepClone(value);
return value.reverse();
}
return value;
},
if (value !== this.value) {
updateValue(value, end) {
if (this.range) {
value = this.handleOverlap(value).map(this.format);
} else {
value = this.format(value);
}
if (!isSameValue(value, this.value)) {
this.$emit('input', value);
}
if (end && value !== this.startValue) {
if (end && !isSameValue(value, this.startValue)) {
this.$emit('change', value);
}
},
@@ -156,8 +200,28 @@ export default createComponent({
[crossAxis]: addUnit(this.barHeight),
};
// 计算选中条的长度百分比
const calcMainAxis = () => {
const { value, min, range, scope } = this;
if (range) {
return `${((value[1] - value[0]) * 100) / scope}%`;
}
return `${((value - min) * 100) / scope}%`;
};
// 计算选中条的开始位置的偏移量
const calcOffset = () => {
const { value, min, range, scope } = this;
if (range) {
return `${((value[0] - min) * 100) / scope}%`;
}
return null;
};
const barStyle = {
[mainAxis]: `${((this.value - this.min) * 100) / this.range}%`,
[mainAxis]: calcMainAxis(),
left: this.vertical ? null : calcOffset(),
top: this.vertical ? calcOffset() : null,
background: this.activeColor,
};
@@ -165,6 +229,47 @@ export default createComponent({
barStyle.transition = 'none';
}
const renderButton = (i) => {
const map = ['left', 'right'];
const isNumber = typeof i === 'number';
const getClassName = () => {
if (isNumber) {
return `button-wrapper-${map[i]}`;
}
return `button-wrapper`;
};
const getRefName = () => {
if (isNumber) {
return `wrapper${i}`;
}
return `wrapper`;
};
return (
<div
ref={getRefName()}
role="slider"
tabindex={this.disabled ? -1 : 0}
aria-valuemin={this.min}
aria-valuenow={this.value}
aria-valuemax={this.max}
aria-orientation={this.vertical ? 'vertical' : 'horizontal'}
class={bem(getClassName())}
onTouchstart={() => {
if (isNumber) {
// 保存当前按钮的索引
this.index = i;
}
}}
onClick={(e) => e.stopPropagation()}
>
{this.slots('button') || (
<div class={bem('button')} style={this.buttonStyle} />
)}
</div>
);
};
return (
<div
style={wrapperStyle}
@@ -172,20 +277,7 @@ export default createComponent({
onClick={this.onClick}
>
<div class={bem('bar')} style={barStyle}>
<div
ref="wrapper"
role="slider"
tabindex={this.disabled ? -1 : 0}
aria-valuemin={this.min}
aria-valuenow={this.value}
aria-valuemax={this.max}
aria-orientation={this.vertical ? 'vertical' : 'horizontal'}
class={bem('button-wrapper')}
>
{this.slots('button') || (
<div class={bem('button')} style={this.buttonStyle} />
)}
</div>
{this.range ? [renderButton(0), renderButton(1)] : renderButton()}
</div>
</div>
);