<template>
<my-range v-model="content" />
</template>
<script>
import myRange from 'myRange'
export default {
data: () => ({
content:''
})
}
</script>
封装页面.vue
<template>
<div class="my-range-slider">
<div class="mrs-runway" @dblclick.stop="onAddPoint">
<div
v-for="(u, x) in info"
:key="u.code"
class="mrs-li"
:style="{ width: u.percent, left: u.startPos }"
@dblclick.stop
>
<el-tooltip ref="T1" placement="top" :content="u.startValue">
<div
class="mrs-btn l"
@dblclick="onDeletePoint(x, true)"
@mousedown.stop="onDragStart($event, x, 'T1')"
/>
</el-tooltip>
<div class="mrs-bar" @mousedown.stop="onDragStart($event, x)" />
<el-tooltip ref="T2" placement="top" :content="u.endValue">
<div
class="mrs-btn r"
@mousedown.stop="onDragStart($event, x, 'T2')"
@dblclick="onDeletePoint(x)" />
</el-tooltip>
</div>
</div>
</div>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'input'
},
props: {
// 4~118;130;162~375
value: { type: String, required: true },
min: { type: Number, default: 0 },
max: { type: Number, default: 300 },
rangeSign: { type: String, default: '~' }, // 范围标识
cutSign: { type: String, default: ';|;' } // 段分割标识
},
data: () => ({
isDraging: false,
info: []
}),
watch: {
value: {
immediate: true,
handler(n) {
try {
const str = (n || '').replace(/\s/g, '');
const array = str ? str.split(new RegExp(this.cutSign)) : ['0'];
const reg = new RegExp(this.rangeSign);
this.info = array.map((u) => {
const [v1, v2] = reg.test(u) ? u.split(reg) : [u, u];
if (isNaN(v1)) {
const errorsign = u.replace(/\d+|;/g, '').substr(0, 1);
const obj = { message: `当前使用分隔符的是${errorsign}, 请属性rangeSign加上="${errorsign}"` };
throw obj;
}
// 防止值超过范围
const v3 = Math.max(+v1, this.min);
const v4 = Math.min(typeof v2 === 'undefined' ? +v1 : +v2, this.max);
// 防止 输入大到小
const v5 = Math.min(v3, v4);
const v6 = Math.max(v3, v4);
return {
code: v5 + '==' + v6,
percent: this.onValueToPercent(v6 - v5),
startPos: this.onValueToPercent(v5 - this.min),
startValue: String(v5),
endValue: String(v6)
};
});
this.$nextTick(this.onEmit);
} catch (e) {
new Error(e.message);
}
}
}
},
methods: {
// 获取移动后的 百分比
onValueToPercent(v) {
const maxValue = this.max - this.min;
return `${(((v) / maxValue) * 100).toFixed(2)}%`;
},
// 获取移动后的 值
onMoveValue(initValue, pageX) {
const maxValue = this.max - this.min;
let v = Math.max(this.min, initValue + (maxValue * pageX) / 100);
v = Math.min(this.max, v);
return String(+v.toFixed(0));
},
// 鼠标按下
onDragStart(e, initIndex, initTRef) {
this.isDraging = false;
window.addEventListener('mousemove', this.onDragging);
window.addEventListener('mouseup', this.onDragEnd);
window.addEventListener('mouseleave', this.onDragEnd);
const row = this.info[initIndex];
const initBarLeftDistance = this.onOffsetLeft(e.target) - this.onOffsetLeft(this.$el);
this.downStart = {
initIndex,
initPercent: +row.percent.replace('%', ''),
initStartPos: +row.startPos.replace('%', ''),
initStartValue: +row.startValue,
initEndtValue: +row.endValue,
initPageX: e.pageX,
initTRef, // tooltip的ref
initIsLeft: initTRef === 'T1', // 左边按钮
initIsRight: initTRef === 'T2', // 右边按钮
initIsCenter: !initTRef, // 中间的bar
// 移动bar时 限制 左边可移动
initBarLeftDistance,
// 移动bar时 限制 右边可移动
initBarRightDistance: this.$el.offsetWidth - initBarLeftDistance - e.target.offsetWidth
};
},
// 鼠标移动
onDragging(e) {
this.isDraging = true;
const { initIndex, initPercent, initStartPos, initStartValue, initEndtValue, initPageX, initTRef, initIsLeft, initIsRight, initBarLeftDistance, initBarRightDistance } = this.downStart;
// pos当前移动百分比 不是像素
let pos = +((e.pageX - initPageX) / this.$el.offsetWidth * 100).toFixed(2);
const row = this.info[initIndex];
if (initIsLeft) { // 左边按钮操作
if (pos <= initPercent) { // 不超过当前段的右边
pos = initStartPos + pos <= 0 ? -initStartPos : pos;
this.$set(row, 'percent', `${initPercent - pos}%`);
this.$set(row, 'startPos', `${initStartPos + pos}%`);
this.$set(row, 'startValue', this.onMoveValue(initStartValue, pos));
} else {
pos = initStartPos + pos >= 100 ? 100 - initStartPos : pos;
this.$set(row, 'percent', `${pos - initPercent}%`);
this.$set(row, 'startPos', `${initStartPos + initPercent}%`);
this.$set(row, 'startValue', String(initEndtValue));
this.$set(row, 'endValue', this.onMoveValue(initEndtValue, pos - initPercent));
}
} else if (initIsRight) { // 右边按钮操作
if (-pos <= initPercent) { // 不超过当前段的左边
pos = initStartPos + pos + initPercent >= 100 ? 100 - initStartPos - initPercent : pos;
this.$set(row, 'percent', `${initPercent + pos}%`);
this.$set(row, 'endValue', this.onMoveValue(initEndtValue, pos));
} else {
pos = initStartPos + pos + initPercent <= 0 ? -initStartPos - initPercent : pos;
this.$set(row, 'percent', `${-pos - initPercent}%`);
this.$set(row, 'startPos', `${initStartPos - (-pos - initPercent)}%`);
this.$set(row, 'startValue', this.onMoveValue(initStartValue, initPercent + pos));
this.$set(row, 'endValue', String(initStartValue));
}
} else { // 中间bar操作
let newPageX = Math.max(-initBarLeftDistance, e.pageX - initPageX);
newPageX = Math.min(initBarRightDistance, newPageX);
pos = +(newPageX / this.$el.offsetWidth * 100).toFixed(2);
this.$set(row, 'startPos', `${initStartPos + pos}%`);
this.$set(row, 'startValue', this.onMoveValue(initStartValue, pos));
this.$set(row, 'endValue', this.onMoveValue(initEndtValue, pos));
}
const TRefdom = this.$refs[initTRef];
initTRef && TRefdom && TRefdom.forEach(u => u.updatePopper());
},
// 鼠标按起
onDragEnd() {
window.removeEventListener('mousemove', this.onDragging);
window.removeEventListener('mouseup', this.onDragEnd);
window.removeEventListener('mouseleave', this.onDragEnd);
if (this.isDraging) {
this.info.map((u) => {
u.code = u.startValue + '==' + u.endValue;
return u;
});
this.$nextTick(this.onEmit);
}
this.isDraging = false;
},
// 增加点
onAddPoint(e) {
window.removeEventListener('mousemove', this.onDragging);
window.removeEventListener('mouseup', this.onDragEnd);
window.removeEventListener('mouseleave', this.onDragEnd);
const outerWidth = this.$el.offsetWidth;
const v = Math.floor(((e.pageX - this.onOffsetLeft(this.$el)) / outerWidth) * (this.max - this.min)) + this.min;
this.info.push({
code: v + '==' + v,
percent: '0%',
startPos: this.onValueToPercent(v),
startValue: String(v),
endValue: String(v)
});
this.$nextTick(this.onEmit);
},
// 删除点
onDeletePoint(index, isLeft) {
const row = this.info[index];
const { startValue: S, endValue: E, startPos, percent } = row;
if (+S !== +E) {
this.$set(row, 'code', !isLeft ? S + '==' + S : E + '==' + E);
this.$set(row, 'percent', '0%');
this.$set(row, 'startPos', !isLeft ? startPos : `${+startPos.replace('%', '') + +percent.replace('%', '')}%`);
this.$set(row, 'startValue', !isLeft ? S : E);
this.$set(row, 'endValue', !isLeft ? S : E);
} else {
this.info.splice(index, 1); // ranges是一个点 删除
}
this.$nextTick(this.onEmit);
},
// 提交更新组件
onEmit() {
let currentMax = [-Infinity, -Infinity];
const result = [];
const array = this.info.sort((a, b) =>
+a.startValue > +b.startValue ? 1 : -1
);
array.forEach((u) => {
const [v1, v2] = [+u.startValue, +u.endValue];
const M = currentMax[1];
const splitRangeSign = this.rangeSign.substr(0, 1);
if (M < v1) {
result.push(v1 === v2 ? v1.toString() : v1 + splitRangeSign + v2);
currentMax = [v1, v2];
} else if (M >= v1 && M < v2) {
result[result.length - 1] = currentMax[0] + splitRangeSign + v2;
currentMax = [+currentMax[0], v2];
} else {
//
}
});
const splitCutSign = this.cutSign.substr(0, 1);
const string = result.join(splitCutSign);
this.$emit('input', string);
this.$emit('change', string);
},
// 距离最左边的距离
onOffsetLeft(dom, distance) {
distance = distance || 0;
if (['BODY', null].includes(dom.offsetParent.nodeName)) {
distance += dom.offsetLeft;
return distance;
} else {
distance += dom.offsetLeft;
return this.onOffsetLeft(dom.offsetParent, distance);
}
}
}
};
</script>
<style lang="less">
@gary: #e4e7ed;
@blue: #409eff;
@height: 6px;
.my-range-slider {
position: relative;
padding: 7px 0;
margin-bottom: 100px;
user-select: none;
.mrs-runway {
height: @height;
background-color: @gary;
border-radius: 3px;
cursor: pointer;
}
.mrs-li {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
height: 100%;
width: 100%;
}
.mrs-bar {
height: @height;
background-color: @blue;
position: absolute;
border-radius: 3px;
top: 7px;
left: 0;
right: 0;
}
.mrs-btn {
position: absolute;
top: 0;
width: 15px;
height: 15px;
border: 2px solid @blue;
background-color: #fff;
border-radius: 15px;
transition: 0.2s;
transition: transform 0.3s;
z-index: 5;
&.l {
left: 0;
margin-left: -10px;
}
&.r {
right: 0;
margin-right: -10px;
}
&:hover {
transform: scale(1.2);
}
}
}
</style>