feat(slider): add dual thumb mode for slider (#7175)

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

* chore: remove comment

* fix: optimize code via CR
This commit is contained in:
被雨水过滤的空气
2020-09-13 12:16:29 +08:00
committed by GitHub
parent 0ce48b9239
commit a745709932
5 changed files with 281 additions and 63 deletions
+112 -35
View File
@@ -2,6 +2,7 @@ import { ref, computed } from 'vue';
// Utils
import { createNamespace, addUnit, getSizeStyle } from '../utils';
import { deepClone } from '../utils/deep-clone';
import { preventDefault } from '../utils/dom/event';
// Composition
@@ -15,6 +16,7 @@ export default createComponent({
props: {
disabled: Boolean,
vertical: Boolean,
range: Boolean,
barHeight: [Number, String],
buttonSize: [Number, String],
activeColor: String,
@@ -32,7 +34,7 @@ export default createComponent({
default: 1,
},
modelValue: {
type: Number,
type: [Number, Array],
default: 0,
},
},
@@ -42,12 +44,13 @@ export default createComponent({
setup(props, { emit, slots }) {
let startValue;
let currentValue;
let index;
const rootRef = ref();
const dragStatus = ref();
const touch = useTouch();
const range = computed(() => props.max - props.min);
const scope = computed(() => props.max - props.min);
const wrapperStyle = computed(() => {
const crossAxis = props.vertical ? 'width' : 'height';
@@ -57,10 +60,30 @@ export default createComponent({
};
});
// 计算选中条的长度百分比
const calcMainAxis = () => {
const { modelValue, min, range } = props;
if (range) {
return `${((modelValue[1] - modelValue[0]) * 100) / scope.value}%`;
}
return `${((modelValue - min) * 100) / scope.value}%`;
};
// 计算选中条的开始位置的偏移量
const calcOffset = () => {
const { modelValue, min, range } = props;
if (range) {
return `${((modelValue[0] - min) * 100) / scope.value}%`;
}
return `0%`;
};
const barStyle = computed(() => {
const mainAxis = props.vertical ? 'height' : 'width';
return {
[mainAxis]: `${((props.modelValue - props.min) * 100) / range.value}%`,
[mainAxis]: calcMainAxis(),
left: props.vertical ? null : calcOffset(),
top: props.vertical ? calcOffset() : null,
background: props.activeColor,
transition: dragStatus.value ? 'none' : null,
};
@@ -72,14 +95,31 @@ export default createComponent({
return Math.round(value / step) * step;
};
const updateValue = (value, end) => {
value = format(value);
const isSameValue = (newValue, oldValue) => {
return JSON.stringify(newValue) === JSON.stringify(oldValue);
};
if (value !== props.modelValue) {
// 处理两个滑块重叠之后的情况
const handleOverlap = (value) => {
if (value[0] > value[1]) {
value = deepClone(value);
return value.reverse();
}
return value;
};
const updateValue = (value, end) => {
if (props.range) {
value = handleOverlap(value).map(format);
} else {
value = format(value);
}
if (!isSameValue(value, props.modelValue)) {
emit('update:modelValue', value);
}
if (end && value !== startValue) {
if (end && !isSameValue(value, startValue)) {
emit('change', value);
}
};
@@ -91,15 +131,26 @@ export default createComponent({
return;
}
const { min, vertical, modelValue } = props;
const { min, vertical, modelValue, range } = props;
const rect = useRect(rootRef);
const delta = vertical
? event.clientY - rect.top
: event.clientX - rect.left;
const total = vertical ? rect.height : rect.width;
const value = +min + (delta / total) * range.value;
let value = +min + (delta / total) * scope.value;
if (range) {
let left = modelValue[0];
let right = modelValue[1];
const middle = (left + right) / 2;
if (value <= middle) {
left = value;
} else {
right = value;
}
value = [left, right];
}
startValue = modelValue;
updateValue(value, true);
};
@@ -109,7 +160,12 @@ export default createComponent({
}
touch.start(event);
startValue = format(props.modelValue);
currentValue = props.modelValue;
if (props.range) {
startValue = props.modelValue.map(format);
} else {
startValue = format(props.modelValue);
}
dragStatus.value = 'start';
};
@@ -129,9 +185,13 @@ export default createComponent({
const rect = useRect(rootRef);
const delta = props.vertical ? touch.deltaY.value : touch.deltaX.value;
const total = props.vertical ? rect.height : rect.width;
const diff = (delta / total) * range.value;
const diff = (delta / total) * scope.value;
currentValue = startValue + diff;
if (props.range) {
currentValue[index] = startValue[index] + diff;
} else {
currentValue = startValue + diff;
}
updateValue(currentValue);
};
@@ -148,27 +208,44 @@ export default createComponent({
dragStatus.value = '';
};
const renderButton = () => (
<div
role="slider"
class={bem('button-wrapper')}
tabindex={props.disabled ? -1 : 0}
aria-valuemin={props.min}
aria-valuenow={props.modelValue}
aria-valuemax={props.max}
aria-orientation={props.vertical ? 'vertical' : 'horizontal'}
onTouchstart={onTouchStart}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
>
{slots.button ? (
slots.button()
) : (
<div class={bem('button')} style={getSizeStyle(props.buttonSize)} />
)}
</div>
);
const renderButton = (i) => {
const map = ['left', 'right'];
const getClassName = () => {
if (typeof i === 'number') {
return `button-wrapper-${map[i]}`;
}
return `button-wrapper`;
};
return (
<div
role="slider"
class={bem(getClassName())}
tabindex={props.disabled ? -1 : 0}
aria-valuemin={props.min}
aria-valuenow={props.modelValue}
aria-valuemax={props.max}
aria-orientation={props.vertical ? 'vertical' : 'horizontal'}
onTouchstart={(e) => {
if (typeof i === 'number') {
// 保存当前按钮的索引
index = i;
}
onTouchStart(e);
}}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
onClick={(e) => e.stopPropagation()}
>
{slots.button ? (
slots.button()
) : (
<div class={bem('button')} style={getSizeStyle(props.buttonSize)} />
)}
</div>
);
};
// format initial value
updateValue(props.modelValue);
@@ -185,7 +262,7 @@ export default createComponent({
onClick={onClick}
>
<div class={bem('bar')} style={barStyle.value}>
{renderButton()}
{props.range ? [renderButton(0), renderButton(1)] : renderButton()}
</div>
</div>
);