720 lines
22 KiB
Vue
720 lines
22 KiB
Vue
|
|
<template>
|
|||
|
|
<view
|
|||
|
|
:style="tools._style([customStyle])"
|
|||
|
|
:class="
|
|||
|
|
tools.cls(classPrefix, [
|
|||
|
|
['top', label || scaleTextArray.length],
|
|||
|
|
['disabled', disabled],
|
|||
|
|
['range', range]
|
|||
|
|
]) +
|
|||
|
|
' ' +
|
|||
|
|
tClass +
|
|||
|
|
' ' +
|
|||
|
|
(vertical ? classPrefix + '--vertical' : '')
|
|||
|
|
"
|
|||
|
|
>
|
|||
|
|
<block v-if="!range">
|
|||
|
|
<text
|
|||
|
|
v-if="showExtremeValue"
|
|||
|
|
:class="classPrefix + '__value ' + classPrefix + '__value--min'"
|
|||
|
|
>
|
|||
|
|
{{ label ? getValue(label, min) : min }}
|
|||
|
|
</text>
|
|||
|
|
<view
|
|||
|
|
id="sliderLine"
|
|||
|
|
:class="tools.cls(classPrefix + '__bar', [['disabled', disabled], theme, ['marks', isScale && theme == 'capsule']]) + ' ' + tClassBar"
|
|||
|
|
@click="onSingleLineTap"
|
|||
|
|
>
|
|||
|
|
<block v-if="isScale">
|
|||
|
|
<view
|
|||
|
|
v-for="(item, index) in scaleArray"
|
|||
|
|
:key="index"
|
|||
|
|
:class="
|
|||
|
|
tools.cls(classPrefix + '__scale-item', [
|
|||
|
|
['active', _value >= item.val],
|
|||
|
|
['disabled', disabled],
|
|||
|
|
theme,
|
|||
|
|
['hidden', ((index == 0 || index == scaleArray.length - 1) && theme == 'capsule') || value == item.val]
|
|||
|
|
])
|
|||
|
|
"
|
|||
|
|
:style="(vertical ? 'top' : 'left') + ':' + item.left + 'px; ' + (vertical ? 'transform: translate(-50%, -50%);' : 'transform: translateX(-50%);')"
|
|||
|
|
:aria-hidden="true"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
v-if="scaleTextArray.length"
|
|||
|
|
:class="tools.cls(classPrefix + '__scale-desc', [theme])"
|
|||
|
|
>
|
|||
|
|
{{ scaleTextArray[index] }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</block>
|
|||
|
|
<view
|
|||
|
|
:class="tools.cls(classPrefix + '__line', [['disabled', disabled], theme, 'single']) + ' ' + tClassBarActive"
|
|||
|
|
:style="(vertical ? 'height' : 'width') + ': ' + lineBarWidth"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
id="singleDot"
|
|||
|
|
:class="classPrefix + '__dot ' + tClassCursor"
|
|||
|
|
@touchstart.stop.prevent="onTouchStart"
|
|||
|
|
@touchmove.stop.prevent="onSingleLineTap"
|
|||
|
|
@touchend.stop.prevent="onTouchEnd"
|
|||
|
|
@touchcancel.stop.prevent="onTouchEnd"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
v-if="label || isVisibleToScreenReader"
|
|||
|
|
:class="tools.cls(classPrefix + '__dot-value', [['sr-only', !label]])"
|
|||
|
|
aria-role="alert"
|
|||
|
|
aria-live="assertive"
|
|||
|
|
:aria-hidden="!isVisibleToScreenReader"
|
|||
|
|
>
|
|||
|
|
{{ getValue(label, _value) || _value }}
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
:class="classPrefix + '__dot-slider'"
|
|||
|
|
aria-role="slider"
|
|||
|
|
:aria-disabled="disabled"
|
|||
|
|
:aria-valuemax="max"
|
|||
|
|
:aria-valuemin="min"
|
|||
|
|
:aria-valuenow="_value"
|
|||
|
|
:aria-valuetext="getValue(label, _value) || _value"
|
|||
|
|
/>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<text
|
|||
|
|
v-if="showExtremeValue"
|
|||
|
|
:class="classPrefix + '__value ' + classPrefix + '__value--max'"
|
|||
|
|
>
|
|||
|
|
{{ label ? getValue(label, max) : max }}
|
|||
|
|
</text>
|
|||
|
|
</block>
|
|||
|
|
<block v-if="range">
|
|||
|
|
<view
|
|||
|
|
v-if="showExtremeValue"
|
|||
|
|
:class="classPrefix + '__range-extreme ' + classPrefix + '__range-extreme--min'"
|
|||
|
|
>
|
|||
|
|
{{ min }}
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
id="sliderLine"
|
|||
|
|
:class="tools.cls(classPrefix + '__bar', [['disabled', disabled], theme, ['marks', isScale && theme == 'capsule']]) + ' ' + tClassBar"
|
|||
|
|
@click="onLineTap"
|
|||
|
|
>
|
|||
|
|
<block v-if="isScale">
|
|||
|
|
<view
|
|||
|
|
v-for="(item, index) in scaleArray"
|
|||
|
|
:key="index"
|
|||
|
|
:class="
|
|||
|
|
tools.cls(classPrefix + '__scale-item', [
|
|||
|
|
['active', dotTopValue[1] >= item.val && item.val >= dotTopValue[0]],
|
|||
|
|
['disabled', disabled],
|
|||
|
|
theme,
|
|||
|
|
['hidden', ((index == 0 || index == scaleArray.length - 1) && theme == 'capsule') || value == item.val]
|
|||
|
|
])
|
|||
|
|
"
|
|||
|
|
:style="(vertical ? 'top' : 'left') + ':' + item.left + 'px; ' + (vertical ? 'transform: translate(-50%, -50%);' : 'transform: translateX(-50%);')"
|
|||
|
|
:aria-hidden="true"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
v-if="scaleTextArray.length"
|
|||
|
|
:class="tools.cls(classPrefix + '__scale-desc', [theme])"
|
|||
|
|
>
|
|||
|
|
{{ scaleTextArray[index] }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</block>
|
|||
|
|
<view
|
|||
|
|
:class="tools.cls(classPrefix + '__line', [['disabled', disabled], theme]) + ' ' + tClassBarActive"
|
|||
|
|
:style="(vertical ? 'top' : 'left') + ': ' + lineLeft + 'px; ' + (vertical ? 'bottom' : 'right') + ': ' + lineRight + 'px'"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
id="leftDot"
|
|||
|
|
:class="classPrefix + '__dot ' + classPrefix + '__dot--left ' + tClassCursor"
|
|||
|
|
@touchstart.stop.prevent="onTouchStart"
|
|||
|
|
@touchmove.stop.prevent="onTouchMoveLeft"
|
|||
|
|
@touchend.stop.prevent="onTouchEnd"
|
|||
|
|
@touchcancel.stop.prevent="onTouchEnd"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
v-if="label || isVisibleToScreenReader"
|
|||
|
|
:class="tools.cls(classPrefix + '__dot-value', [['sr-only', !label]])"
|
|||
|
|
aria-role="alert"
|
|||
|
|
aria-live="assertive"
|
|||
|
|
:aria-hidden="!isVisibleToScreenReader"
|
|||
|
|
>
|
|||
|
|
{{ getValue(label, dotTopValue[0]) || dotTopValue[0] }}
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
:class="classPrefix + '__dot-slider'"
|
|||
|
|
aria-role="slider"
|
|||
|
|
:aria-disabled="disabled"
|
|||
|
|
:aria-valuemax="max"
|
|||
|
|
:aria-valuemin="min"
|
|||
|
|
:aria-valuenow="dotTopValue[0]"
|
|||
|
|
:aria-valuetext="getValue(label, dotTopValue[0]) || dotTopValue[0]"
|
|||
|
|
/>
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
id="rightDot"
|
|||
|
|
:class="classPrefix + '__dot ' + classPrefix + '__dot--right ' + tClassCursor"
|
|||
|
|
@touchstart.stop.prevent="onTouchStart"
|
|||
|
|
@touchmove.stop.prevent="onTouchMoveRight"
|
|||
|
|
@touchend.stop.prevent="onTouchEnd"
|
|||
|
|
@touchcancel.stop.prevent="onTouchEnd"
|
|||
|
|
>
|
|||
|
|
<view
|
|||
|
|
v-if="label || isVisibleToScreenReader"
|
|||
|
|
:class="tools.cls(classPrefix + '__dot-value', [['sr-only', !label]])"
|
|||
|
|
aria-role="alert"
|
|||
|
|
aria-live="assertive"
|
|||
|
|
:aria-hidden="!isVisibleToScreenReader"
|
|||
|
|
>
|
|||
|
|
{{ getValue(label, dotTopValue[1]) || dotTopValue[1] }}
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
:class="classPrefix + '__dot-slider'"
|
|||
|
|
aria-role="slider"
|
|||
|
|
:aria-disabled="disabled"
|
|||
|
|
:aria-valuemax="max"
|
|||
|
|
:aria-valuemin="min"
|
|||
|
|
:aria-valuenow="dotTopValue[1]"
|
|||
|
|
:aria-valuetext="getValue(label, dotTopValue[1]) || dotTopValue[1]"
|
|||
|
|
/>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
v-if="showExtremeValue"
|
|||
|
|
:class="classPrefix + '__range-extreme ' + classPrefix + '__range-extreme--max'"
|
|||
|
|
>
|
|||
|
|
{{ max }}
|
|||
|
|
</view>
|
|||
|
|
</block>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
<script>
|
|||
|
|
import { uniComponent } from '../common/src/index';
|
|||
|
|
import { prefix } from '../common/config';
|
|||
|
|
import { trimSingleValue, trimValue } from './tool';
|
|||
|
|
import props from './props';
|
|||
|
|
import { getRect, coalesce, nextTick } from '../common/utils';
|
|||
|
|
import Bus from '../common/bus';
|
|||
|
|
import tools from '../common/utils.wxs';
|
|||
|
|
import { getValue } from './computed.js';
|
|||
|
|
import { isString, isFunction } from '../common/validator';
|
|||
|
|
|
|||
|
|
|
|||
|
|
const name = `${prefix}-slider`;
|
|||
|
|
|
|||
|
|
export default uniComponent({
|
|||
|
|
name,
|
|||
|
|
options: {
|
|||
|
|
styleIsolation: 'shared',
|
|||
|
|
},
|
|||
|
|
controlledProps: [
|
|||
|
|
{
|
|||
|
|
key: 'value',
|
|||
|
|
event: 'change',
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
externalClasses: [
|
|||
|
|
`${prefix}-class`,
|
|||
|
|
`${prefix}-class-bar`,
|
|||
|
|
`${prefix}-class-bar-active`,
|
|||
|
|
`${prefix}-class-bar-disabled`,
|
|||
|
|
`${prefix}-class-cursor`,
|
|||
|
|
],
|
|||
|
|
props: {
|
|||
|
|
...props,
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
// 按钮样式列表
|
|||
|
|
sliderStyles: '',
|
|||
|
|
classPrefix: name,
|
|||
|
|
initialLeft: null,
|
|||
|
|
initialRight: null,
|
|||
|
|
activeLeft: 0,
|
|||
|
|
activeRight: 0,
|
|||
|
|
maxRange: 0,
|
|||
|
|
lineLeft: 0,
|
|||
|
|
lineRight: 0,
|
|||
|
|
dotTopValue: [0, 0],
|
|||
|
|
_value: 0,
|
|||
|
|
blockSize: 20,
|
|||
|
|
isScale: false,
|
|||
|
|
scaleArray: [],
|
|||
|
|
scaleTextArray: [],
|
|||
|
|
prefix,
|
|||
|
|
realLabel: '',
|
|||
|
|
extremeLabel: [],
|
|||
|
|
isVisibleToScreenReader: false,
|
|||
|
|
identifier: [-1, -1],
|
|||
|
|
__inited: false,
|
|||
|
|
tools,
|
|||
|
|
|
|||
|
|
lineBarWidth: 0,
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
watch: {
|
|||
|
|
value: {
|
|||
|
|
handler(newValue) {
|
|||
|
|
this.handlePropsChange(newValue);
|
|||
|
|
},
|
|||
|
|
// immediate: true,
|
|||
|
|
deep: true,
|
|||
|
|
},
|
|||
|
|
_value: {
|
|||
|
|
handler(newValue) {
|
|||
|
|
this.bus.on('initial', () => this.renderLine(newValue));
|
|||
|
|
this.toggleA11yTips();
|
|||
|
|
},
|
|||
|
|
// immediate: true,
|
|||
|
|
deep: true,
|
|||
|
|
},
|
|||
|
|
marks: {
|
|||
|
|
handler(val) {
|
|||
|
|
this.bus.on('initial', () => this.handleMark(val));
|
|||
|
|
},
|
|||
|
|
// immediate: true,
|
|||
|
|
deep: true,
|
|||
|
|
},
|
|||
|
|
label: {
|
|||
|
|
handler(val) {
|
|||
|
|
this.isShowLabel = Boolean(val);
|
|||
|
|
},
|
|||
|
|
immediate: true,
|
|||
|
|
},
|
|||
|
|
showExtremeValue: 'getwExtremeLabel',
|
|||
|
|
min: 'getwExtremeLabel',
|
|||
|
|
max: 'getwExtremeLabel',
|
|||
|
|
},
|
|||
|
|
created() {
|
|||
|
|
this.bus = new Bus();
|
|||
|
|
this.bus.on('initial', () => this.handleMark(this.marks));
|
|||
|
|
this.bus.on('initial', () => {
|
|||
|
|
nextTick().then(() => {
|
|||
|
|
this.renderLine(this._value);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
this.toggleA11yTips();
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
const { value, defaultValue } = this;
|
|||
|
|
// if (!value)
|
|||
|
|
this.handlePropsChange(coalesce(value, defaultValue, 0));
|
|||
|
|
this.init();
|
|||
|
|
this.injectPageScroll();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
getValue,
|
|||
|
|
getwExtremeLabel() {
|
|||
|
|
const { showExtremeValue, min, max } = this;
|
|||
|
|
if (!showExtremeValue) return;
|
|||
|
|
|
|||
|
|
this.extremeLabel = [this.getLabelByValue(Number(min), 'min'), this.getLabelByValue(Number(max), 'max')];
|
|||
|
|
},
|
|||
|
|
injectPageScroll() {
|
|||
|
|
const { range, vertical } = this;
|
|||
|
|
if (!range || !vertical) return;
|
|||
|
|
const pages = getCurrentPages() || [];
|
|||
|
|
let curPage = null;
|
|||
|
|
if (pages && pages.length - 1 >= 0) {
|
|||
|
|
curPage = pages[pages.length - 1];
|
|||
|
|
}
|
|||
|
|
if (!curPage) return;
|
|||
|
|
const originPageScroll = curPage?.onPageScroll;
|
|||
|
|
curPage.onPageScroll = (rest) => {
|
|||
|
|
originPageScroll?.call(this, rest);
|
|||
|
|
this.observerScrollTop(rest);
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
observerScrollTop(rest) {
|
|||
|
|
const { scrollTop } = rest || {};
|
|||
|
|
this.pageScrollTop = scrollTop;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
toggleA11yTips() {
|
|||
|
|
this.isVisibleToScreenReader = true;
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.isVisibleToScreenReader = false;
|
|||
|
|
}, 2000);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
renderLine(val) {
|
|||
|
|
const { min, max, range } = this;
|
|||
|
|
const { maxRange } = this;
|
|||
|
|
|
|||
|
|
if (range) {
|
|||
|
|
const left = (maxRange * (val[0] - Number(min))) / (Number(max) - Number(min));
|
|||
|
|
const right = (maxRange * (Number(max) - val[1])) / (Number(max) - Number(min));
|
|||
|
|
// 因为要计算点相对于线的绝对定位,所以要取整条线的长度而非可滑动的范围
|
|||
|
|
this.setLineStyle(left, right);
|
|||
|
|
} else {
|
|||
|
|
this.setSingleBarWidth(val);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
triggerValue(value) {
|
|||
|
|
const { min, max, range } = this;
|
|||
|
|
const trimmedValue = trimValue(value, {
|
|||
|
|
min, max, range,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (JSON.stringify(this.preval) === JSON.stringify(trimmedValue)) return;
|
|||
|
|
this.preval = value;
|
|||
|
|
|
|||
|
|
this._trigger('change', {
|
|||
|
|
value: trimmedValue,
|
|||
|
|
});
|
|||
|
|
if (this._selfControlled) {
|
|||
|
|
this._value = trimmedValue;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
getLabelByValue(value, position) {
|
|||
|
|
const { label } = this;
|
|||
|
|
|
|||
|
|
if (isString(label)) {
|
|||
|
|
let text = String(value);
|
|||
|
|
try {
|
|||
|
|
const rule = /\${value}%/g;
|
|||
|
|
const enableToReplace = rule.test(label);
|
|||
|
|
if (enableToReplace) {
|
|||
|
|
text = label.replace(rule, String(value));
|
|||
|
|
} else {
|
|||
|
|
text = label;
|
|||
|
|
throw new Error();
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
return text;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isFunction(label)) {
|
|||
|
|
return label(value, position);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return String(value);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
handlePropsChange(newValue) {
|
|||
|
|
const { min, max, range } = this;
|
|||
|
|
|
|||
|
|
const value = trimValue(newValue, {
|
|||
|
|
min, max, range,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const realLabel = this.getLabelByValue(value);
|
|||
|
|
|
|||
|
|
// 避免受控模式下死循环,同时不影响初始化后的首次点击
|
|||
|
|
if (this.preval !== undefined) {
|
|||
|
|
this.preval = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const setValueAndTrigger = () => {
|
|||
|
|
this._value = value;
|
|||
|
|
this.realLabel = realLabel;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 基本样式未初始化,等待初始化后在改变数据。
|
|||
|
|
if (this.maxRange === 0) {
|
|||
|
|
this.init().then(setValueAndTrigger);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setValueAndTrigger();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
valueToPosition(value) {
|
|||
|
|
const { min, max, theme } = this;
|
|||
|
|
const { blockSize, maxRange } = this;
|
|||
|
|
const halfBlock = (theme) === 'capsule' ? Number(blockSize) / 2 : 0;
|
|||
|
|
|
|||
|
|
return Math.round(((Number(value) - Number(min)) / (Number(max) - Number(min))) * maxRange) + halfBlock;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
handleMark(marks) {
|
|||
|
|
const calcPos = arr => arr.map(item => ({
|
|||
|
|
val: item,
|
|||
|
|
left: this.valueToPosition(item),
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
if (marks?.length && Array.isArray(marks)) {
|
|||
|
|
this.isScale = true;
|
|||
|
|
this.scaleArray = calcPos(marks);
|
|||
|
|
this.scaleTextArray = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Object.prototype.toString.call(marks) === '[object Object]') {
|
|||
|
|
const scaleArray = Object.keys(marks).map(item => Number(item));
|
|||
|
|
const scaleTextArray = scaleArray.map(item => marks[item]);
|
|||
|
|
|
|||
|
|
this.isScale = scaleArray.length > 0;
|
|||
|
|
this.scaleArray = calcPos(scaleArray);
|
|||
|
|
this.scaleTextArray = scaleTextArray;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
setSingleBarWidth(value) {
|
|||
|
|
const width = this.valueToPosition(value);
|
|||
|
|
|
|||
|
|
this.lineBarWidth = `${width}px`;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async init() {
|
|||
|
|
if (this.__inited) return;
|
|||
|
|
await this.getInitialInfo();
|
|||
|
|
this.__inited = true;
|
|||
|
|
this.bus.emit('initial');
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async getInitialInfo() {
|
|||
|
|
const line = await getRect(this, '#sliderLine');
|
|||
|
|
const { blockSize } = this;
|
|||
|
|
const { theme, vertical } = this;
|
|||
|
|
const halfBlock = Number(blockSize) / 2;
|
|||
|
|
const { top, bottom, right, left } = line;
|
|||
|
|
let maxRange = vertical ? bottom - top : right - left;
|
|||
|
|
let initialLeft = vertical ? top : left;
|
|||
|
|
let initialRight = vertical ? bottom : right;
|
|||
|
|
if (initialLeft === 0 && initialRight === 0) return;
|
|||
|
|
|
|||
|
|
if ((theme) === 'capsule') {
|
|||
|
|
maxRange = maxRange - Number(blockSize) - 6; // 6 是边框宽度
|
|||
|
|
initialLeft -= halfBlock;
|
|||
|
|
initialRight -= halfBlock;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.maxRange = maxRange;
|
|||
|
|
this.initialLeft = initialLeft;
|
|||
|
|
this.initialRight = initialRight;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
stepValue(value) {
|
|||
|
|
const { step, min, max } = this;
|
|||
|
|
const decimal = String(step).indexOf('.') > -1 ? String(step).length - String(step).indexOf('.') - 1 : 0;
|
|||
|
|
const closestStep = trimSingleValue(
|
|||
|
|
Number((Math.round(value / Number(step)) * Number(step)).toFixed(decimal)),
|
|||
|
|
Number(min),
|
|||
|
|
Number(max),
|
|||
|
|
);
|
|||
|
|
return closestStep;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 点击滑动条的事件
|
|||
|
|
async onSingleLineTap(e) {
|
|||
|
|
await this.getInitialInfo();
|
|||
|
|
const { disabled } = this;
|
|||
|
|
if (disabled) return;
|
|||
|
|
const isSingleLineTap = this.identifier[0] === -1; // 区分点击滑动条和单游标的滑动
|
|||
|
|
if (isSingleLineTap) {
|
|||
|
|
const [touch] = e.changedTouches;
|
|||
|
|
this.identifier[0] = touch.identifier;
|
|||
|
|
}
|
|||
|
|
const value = await this.getSingleChangeValue(e);
|
|||
|
|
|
|||
|
|
if (isSingleLineTap) {
|
|||
|
|
this.identifier[0] = -1;
|
|||
|
|
}
|
|||
|
|
this.triggerValue(value);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async getSingleChangeValue(e) {
|
|||
|
|
// await this.getInitialInfo();
|
|||
|
|
const { min, max, theme, vertical } = this;
|
|||
|
|
const { initialLeft, maxRange, blockSize } = this;
|
|||
|
|
const touch = e.changedTouches.find(item => item.identifier === this.identifier[0]);
|
|||
|
|
if (!touch) return;
|
|||
|
|
const pagePosition = this.getPagePosition(touch);
|
|||
|
|
|
|||
|
|
let offset = 0;
|
|||
|
|
if ((theme) === 'capsule') {
|
|||
|
|
offset = Number(blockSize);
|
|||
|
|
if (vertical) {
|
|||
|
|
}
|
|||
|
|
offset += 3;
|
|||
|
|
} else if (vertical) {
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const currentLeft = pagePosition - initialLeft - offset;
|
|||
|
|
let value = 0;
|
|||
|
|
if (currentLeft <= 0) {
|
|||
|
|
value = Number(min);
|
|||
|
|
} else if (currentLeft >= maxRange) {
|
|||
|
|
value = Number(max);
|
|||
|
|
} else {
|
|||
|
|
value = (currentLeft / maxRange) * (Number(max) - Number(min)) + Number(min);
|
|||
|
|
}
|
|||
|
|
return this.stepValue(value);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将位置转换为值
|
|||
|
|
*
|
|||
|
|
* @param {number} posValue 位置数据
|
|||
|
|
* @param {(0 | 1)} dir 方向: 0-left, 1-right
|
|||
|
|
* @return {number}
|
|||
|
|
* @memberof Slider
|
|||
|
|
*/
|
|||
|
|
convertPosToValue(posValue, dir) {
|
|||
|
|
const { maxRange } = this;
|
|||
|
|
const { max, min } = this;
|
|||
|
|
return dir === 0
|
|||
|
|
? (posValue / maxRange) * (Number(max) - Number(min)) + Number(min)
|
|||
|
|
: Number(max) - (posValue / maxRange) * (Number(max) - Number(min));
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 点击范围选择滑动条的事件
|
|||
|
|
onLineTap(e) {
|
|||
|
|
const { disabled, theme, vertical } = this;
|
|||
|
|
const { initialLeft, initialRight, maxRange, blockSize } = this;
|
|||
|
|
if (disabled) return;
|
|||
|
|
|
|||
|
|
const [touch] = e.changedTouches;
|
|||
|
|
const pagePosition = this.getPagePosition(touch);
|
|||
|
|
const halfBlock = (theme) === 'capsule' ? Number(blockSize) / 2 : 0;
|
|||
|
|
|
|||
|
|
const currentLeft = pagePosition - initialLeft;
|
|||
|
|
const currentRight = -(pagePosition - initialRight);
|
|||
|
|
if (currentLeft < 0 || currentRight > maxRange + Number(blockSize)) return;
|
|||
|
|
|
|||
|
|
Promise.all([getRect(this, '#leftDot'), getRect(this, '#rightDot')]).then(([leftDot, rightDot]) => {
|
|||
|
|
const pageScrollTop = this.pageScrollTop || 0;
|
|||
|
|
// 点击处-halfblock 与 leftDot左侧的距离(绝对值)
|
|||
|
|
const leftDotPosition = vertical ? leftDot.top + pageScrollTop : leftDot.left;
|
|||
|
|
const distanceLeft = Math.abs(pagePosition - leftDotPosition - halfBlock);
|
|||
|
|
// 点击处-halfblock 与 rightDot左侧的距离(绝对值)
|
|||
|
|
const rightDotPosition = vertical ? rightDot.top + pageScrollTop : rightDot.left;
|
|||
|
|
const distanceRight = Math.abs(rightDotPosition - pagePosition + halfBlock);
|
|||
|
|
// 哪个绝对值小就移动哪个Dot
|
|||
|
|
const isMoveLeft = distanceLeft < distanceRight;
|
|||
|
|
|
|||
|
|
let offset = 0;
|
|||
|
|
if ((theme) === 'capsule') {
|
|||
|
|
offset = Number(blockSize);
|
|||
|
|
if (vertical) {
|
|||
|
|
offset *= 2;
|
|||
|
|
}
|
|||
|
|
offset += 6;
|
|||
|
|
} else if (vertical) {
|
|||
|
|
offset = Number(blockSize);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isMoveLeft) {
|
|||
|
|
// 当前leftdot中心 + 左侧偏移量 = 目标左侧中心距离
|
|||
|
|
const left = pagePosition - initialLeft - offset;
|
|||
|
|
const leftValue = this.convertPosToValue(left, 0);
|
|||
|
|
this.triggerValue([this.stepValue(leftValue), this._value[1]]);
|
|||
|
|
} else {
|
|||
|
|
let right = -(pagePosition - initialRight);
|
|||
|
|
if (vertical) {
|
|||
|
|
right += offset / 2;
|
|||
|
|
}
|
|||
|
|
const rightValue = this.convertPosToValue(right, 1);
|
|||
|
|
|
|||
|
|
this.triggerValue([this._value[0], this.stepValue(rightValue)]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async onTouchStart(e) {
|
|||
|
|
await this.getInitialInfo();
|
|||
|
|
this.$emit('dragstart', { e });
|
|||
|
|
const [touch] = e.changedTouches;
|
|||
|
|
if (e.currentTarget.id === 'rightDot') {
|
|||
|
|
this.identifier[1] = touch.identifier;
|
|||
|
|
} else {
|
|||
|
|
this.identifier[0] = touch.identifier;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onTouchMoveLeft(e) {
|
|||
|
|
const { disabled, theme } = this;
|
|||
|
|
const { initialLeft, _value, blockSize } = this;
|
|||
|
|
if (disabled) return;
|
|||
|
|
|
|||
|
|
const touch = e.changedTouches.find(item => item.identifier === this.identifier[0]);
|
|||
|
|
if (!touch) return;
|
|||
|
|
const pagePosition = this.getPagePosition(touch);
|
|||
|
|
let offset = 0;
|
|||
|
|
if ((theme) === 'capsule') {
|
|||
|
|
offset = Number(blockSize) + 3;
|
|||
|
|
}
|
|||
|
|
const currentLeft = pagePosition - initialLeft - offset;
|
|||
|
|
|
|||
|
|
const newData = [...(_value)];
|
|||
|
|
const leftValue = this.convertPosToValue(currentLeft, 0);
|
|||
|
|
|
|||
|
|
newData[0] = this.stepValue(leftValue);
|
|||
|
|
|
|||
|
|
this.triggerValue(newData);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onTouchMoveRight(e) {
|
|||
|
|
const { disabled, vertical, theme } = this;
|
|||
|
|
const { initialRight, _value } = this;
|
|||
|
|
if (disabled) return;
|
|||
|
|
|
|||
|
|
const touch = e.changedTouches.find(item => item.identifier === this.identifier[1]);
|
|||
|
|
if (!touch) return;
|
|||
|
|
|
|||
|
|
const pagePosition = this.getPagePosition(touch);
|
|||
|
|
let offset = 0;
|
|||
|
|
if ((theme) === 'capsule') {
|
|||
|
|
offset -= 3;
|
|||
|
|
} else if (vertical) {
|
|||
|
|
}
|
|||
|
|
const currentRight = -(pagePosition - initialRight - offset);
|
|||
|
|
|
|||
|
|
const newData = [...(_value)];
|
|||
|
|
const rightValue = this.convertPosToValue(currentRight, 1);
|
|||
|
|
newData[1] = this.stepValue(rightValue);
|
|||
|
|
|
|||
|
|
this.triggerValue(newData);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
setLineStyle(left, right) {
|
|||
|
|
const { theme } = this;
|
|||
|
|
const { blockSize, maxRange } = this;
|
|||
|
|
const halfBlock = (theme) === 'capsule' ? Number(blockSize) / 2 : 0;
|
|||
|
|
const [a, b] = this._value ;
|
|||
|
|
const cut = v => parseInt(v, 10);
|
|||
|
|
|
|||
|
|
this.dotTopValue = [a, b];
|
|||
|
|
this.realLabel = [this.getLabelByValue(a, 'start'), this.getLabelByValue(b, 'end')];
|
|||
|
|
|
|||
|
|
if (left + right <= maxRange) {
|
|||
|
|
this.lineLeft = cut(left + halfBlock);
|
|||
|
|
this.lineRight = cut(right + halfBlock);
|
|||
|
|
} else {
|
|||
|
|
this.lineLeft = cut(maxRange + halfBlock - right);
|
|||
|
|
this.lineRight = cut(maxRange - left + halfBlock * 1.5);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onTouchEnd(e) {
|
|||
|
|
this.$emit('dragend', { e, value: this._value });
|
|||
|
|
if (e.currentTarget.id === 'rightDot') {
|
|||
|
|
this.identifier[1] = -1;
|
|||
|
|
} else {
|
|||
|
|
this.identifier[0] = -1;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
getPagePosition(touch) {
|
|||
|
|
const { clientX, clientY } = touch;
|
|||
|
|
const { vertical } = this;
|
|||
|
|
return vertical ? clientY : clientX;
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
<style scoped>
|
|||
|
|
@import './slider.css';
|
|||
|
|
</style>
|