Last active
August 3, 2023 11:12
-
-
Save da411d/6b399a87b3131071adbce5cbb8cafdcf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div | |
ref="wrap" | |
:id="id" | |
class="vue-slide-bar-component vue-slide-bar-horizontal" | |
:style="calculateHeight" | |
@click="wrapClick" | |
> | |
<div | |
ref="elem" | |
class="vue-slide-bar" | |
:style="{ | |
height: `${lineHeight}px` | |
}" | |
> | |
<!-- <template>--> | |
<div | |
ref="tooltip" | |
class="vue-slide-bar-always vue-slide-bar-tooltip-container" | |
:style="{ width: `${iconWidth}px` }" | |
@mousedown="moveStart" | |
@touchstart="moveStart" | |
> | |
<span v-if="showTooltip" class="vue-slide-bar-tooltip-top vue-slide-bar-tooltip-wrap"> | |
<slot name="tooltip"> | |
<span :style="tooltipStyles" class="vue-slide-bar-tooltip"> | |
{{ val }} | |
</span> | |
</slot> | |
</span> | |
</div> | |
<!-- </template>--> | |
<div ref="process" :style="processStyle" class="vue-slide-bar-process" /> | |
</div> | |
<div v-if="range" class="vue-slide-bar-range"> | |
<div | |
v-for="(r, index) in range" | |
:key="index" | |
class="vue-slide-bar-separate" | |
:style="dataLabelStyles" | |
> | |
<span v-if="!r.isHide" class="vue-slide-bar-separate-text"> | |
{{ r.label }} | |
</span> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: 'vue-slide-bar', | |
data() { | |
return { | |
flag: false, | |
size: 0, | |
currentValue: 0, | |
currentSlider: 0, | |
isComponentExists: true, | |
interval: 1, | |
lazy: false, | |
realTime: false, | |
dataLabelStyles: { | |
color: '#4a4a4a', | |
'font-family': 'Arial, sans-serif', | |
'font-size': '12px', | |
...this.$props.labelStyles | |
} | |
}; | |
}, | |
props: { | |
data: { | |
type: Array, | |
default: null | |
}, | |
id: { | |
type: String, | |
default: 'wrap' | |
}, | |
range: { | |
type: Array, | |
default: null | |
}, | |
speed: { | |
type: Number, | |
default: 0.5 | |
}, | |
lineHeight: { | |
type: Number, | |
default: 5 | |
}, | |
iconWidth: { | |
type: Number, | |
default: 20 | |
}, | |
modelValue: { | |
type: [String, Number], | |
default: 0 | |
}, | |
min: { | |
type: Number, | |
default: 0 | |
}, | |
max: { | |
type: Number, | |
default: 100 | |
}, | |
showTooltip: { | |
type: Boolean, | |
default: true | |
}, | |
isDisabled: { | |
type: Boolean, | |
default: false | |
}, | |
draggable: { | |
type: Boolean, | |
default: true | |
}, | |
paddingless: { | |
type: Boolean, | |
default: false | |
}, | |
tooltipStyles: Object, | |
labelStyles: Object, | |
processStyle: Object | |
}, | |
emits: ['dragStart', 'dragEnd', 'callbackRange', 'update:modelValue'], | |
computed: { | |
slider() { | |
return this.$refs.tooltip; | |
}, | |
val: { | |
get() { | |
return this.data ? this.data[this.currentValue] : this.currentValue; | |
}, | |
set(val) { | |
if (this.data) { | |
let index = this.data.indexOf(val); | |
if (index > -1) { | |
this.currentValue = index; | |
} | |
} else { | |
this.currentValue = val; | |
} | |
} | |
}, | |
currentIndex() { | |
return (this.currentValue - this.minimum) / this.spacing; | |
}, | |
indexRange() { | |
return [0, this.currentIndex]; | |
}, | |
minimum() { | |
return this.data ? 0 : this.min; | |
}, | |
maximum() { | |
return this.data ? this.data.length - 1 : this.max; | |
}, | |
multiple() { | |
let decimals = `${this.interval}`.split('.')[1]; | |
return decimals ? Math.pow(10, decimals.length) : 1; | |
}, | |
spacing() { | |
return this.data ? 1 : this.interval; | |
}, | |
total() { | |
if (this.data) { | |
return this.data.length - 1; | |
} else if ( | |
Math.floor((this.maximum - this.minimum) * this.multiple) % | |
(this.interval * this.multiple) !== | |
0 | |
) { | |
this.printError( | |
'[VueSlideBar error]: Prop[interval] is illegal, Please make sure that the interval can be divisible' | |
); | |
} | |
return (this.maximum - this.minimum) / this.interval; | |
}, | |
gap() { | |
return this.size / this.total; | |
}, | |
position() { | |
return ((this.currentValue - this.minimum) / this.spacing) * this.gap; | |
}, | |
limit() { | |
return [0, this.size]; | |
}, | |
valueLimit() { | |
return [this.minimum, this.maximum]; | |
}, | |
calculateHeight() { | |
return this.paddingless | |
? {} | |
: { 'padding-top': '40px', 'min-height': this.range ? '100px' : null }; | |
} | |
}, | |
watch: { | |
modelValue(val) { | |
if (this.flag) this.setValue(val); | |
else this.setValue(val, this.speed); | |
}, | |
max(val) { | |
if (val < this.min) { | |
return this.printError( | |
'[VueSlideBar error]: The maximum value can not be less than the minimum value.' | |
); | |
} | |
let resetVal = this.limitValue(this.val); | |
this.setValue(resetVal); | |
this.refresh(); | |
}, | |
min(val) { | |
if (val > this.max) { | |
return this.printError( | |
'[VueSlideBar error]: The minimum value can not be greater than the maximum value.' | |
); | |
} | |
let resetVal = this.limitValue(this.val); | |
this.setValue(resetVal); | |
this.refresh(); | |
} | |
}, | |
methods: { | |
bindEvents() { | |
document.addEventListener('touchmove', this.moving, { passive: false }); | |
document.addEventListener('touchend', this.moveEnd, { passive: false }); | |
document.addEventListener('mousemove', this.moving); | |
document.addEventListener('mouseup', this.moveEnd); | |
document.addEventListener('mouseleave', this.moveEnd); | |
window.addEventListener('resize', this.refresh); | |
}, | |
unbindEvents() { | |
window.removeEventListener('resize', this.refresh); | |
document.removeEventListener('touchmove', this.moving); | |
document.removeEventListener('touchend', this.moveEnd); | |
document.removeEventListener('mousemove', this.moving); | |
document.removeEventListener('mouseup', this.moveEnd); | |
document.removeEventListener('mouseleave', this.moveEnd); | |
}, | |
getPos(e) { | |
this.realTime && this.getStaticData(); | |
return e.clientX - this.offset; | |
}, | |
wrapClick(e) { | |
if (this.isDisabled || (!this.draggable && e.target.id === this.id)) return false; | |
let pos = this.getPos(e); | |
this.setValueOnPos(pos); | |
}, | |
moveStart(e, index) { | |
if (!this.draggable) return false; | |
this.flag = true; | |
this.$emit('dragStart', this); | |
}, | |
moving(e) { | |
if (!this.flag || !this.draggable) return false; | |
e.preventDefault(); | |
if (e.targetTouches && e.targetTouches[0]) e = e.targetTouches[0]; | |
this.setValueOnPos(this.getPos(e), true); | |
}, | |
moveEnd(e) { | |
if (this.flag && this.draggable) { | |
this.$emit('dragEnd', this); | |
if (this.lazy && this.isDiff(this.val, this.modelValue)) { | |
this.syncValue(); | |
} | |
} else { | |
return false; | |
} | |
this.flag = false; | |
this.setPosition(); | |
}, | |
setValueOnPos(pos, isDrag) { | |
let range = this.limit; | |
let valueRange = this.valueLimit; | |
if (pos >= range[0] && pos <= range[1]) { | |
this.setTransform(pos); | |
let v = | |
(Math.round(pos / this.gap) * (this.spacing * this.multiple) + | |
this.minimum * this.multiple) / | |
this.multiple; | |
this.setCurrentValue(v, isDrag); | |
} else if (pos < range[0]) { | |
this.setTransform(range[0]); | |
this.setCurrentValue(valueRange[0]); | |
if (this.currentSlider === 1) this.currentSlider = 0; | |
} else { | |
this.setTransform(range[1]); | |
this.setCurrentValue(valueRange[1]); | |
if (this.currentSlider === 0) this.currentSlider = 1; | |
} | |
}, | |
isDiff(a, b) { | |
if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) { | |
return true; | |
} else if (Array.isArray(a) && a.length === b.length) { | |
return a.some((v, i) => v !== b[i]); | |
} | |
return a !== b; | |
}, | |
setCurrentValue(val, bool) { | |
if (val < this.minimum || val > this.maximum) return false; | |
if (this.isDiff(this.currentValue, val)) { | |
this.currentValue = val; | |
if (!this.lazy || !this.flag) { | |
this.syncValue(); | |
} | |
} | |
bool || this.setPosition(); | |
}, | |
setIndex(val) { | |
val = this.spacing * val + this.minimum; | |
this.setCurrentValue(val); | |
}, | |
setValue(val, speed) { | |
if (this.isDiff(this.val, val)) { | |
let resetVal = this.limitValue(val); | |
this.val = resetVal; | |
this.syncValue(); | |
} | |
this.$nextTick(() => this.setPosition(speed)); | |
}, | |
setPosition(speed) { | |
if (!this.flag) this.setTransitionTime(speed === undefined ? this.speed : speed); | |
else this.setTransitionTime(0); | |
this.setTransform(this.position); | |
}, | |
setTransform(val) { | |
let value = val - (this.$refs.tooltip.scrollWidth - 2) / 2; | |
let translateValue = `translateX(${value}px)`; | |
this.slider.style.transform = translateValue; | |
this.slider.style.WebkitTransform = translateValue; | |
this.slider.style.msTransform = translateValue; | |
this.$refs.process.style.width = `${val}px`; | |
this.$refs.process.style['left'] = 0; | |
}, | |
setTransitionTime(time) { | |
this.slider.style.transitionDuration = `${time}s`; | |
this.slider.style.WebkitTransitionDuration = `${time}s`; | |
this.$refs.process.style.transitionDuration = `${time}s`; | |
this.$refs.process.style.WebkitTransitionDuration = `${time}s`; | |
}, | |
limitValue(val) { | |
if (this.data) { | |
return val; | |
} | |
const inRange = (v) => { | |
if (v < this.min) { | |
this.printError( | |
`[VueSlideBar warn]: The value of the slider is ${val}, the minimum value is ${this.min}, the value of this slider can not be less than the minimum value` | |
); | |
return this.min; | |
} else if (v > this.max) { | |
this.printError( | |
`[VueSlideBar warn]: The value of the slider is ${val}, the maximum value is ${this.max}, the value of this slider can not be greater than the maximum value` | |
); | |
return this.max; | |
} | |
return v; | |
}; | |
return inRange(val); | |
}, | |
syncValue() { | |
let val = this.val; | |
if (this.range) { | |
this.$emit('callbackRange', this.range[this.currentIndex]); | |
} | |
this.$emit('update:modelValue', val); | |
}, | |
getValue() { | |
return this.val; | |
}, | |
getIndex() { | |
return this.currentIndex; | |
}, | |
getStaticData() { | |
if (this.$refs.elem) { | |
this.size = this.$refs.elem.offsetWidth; | |
this.offset = this.$refs.elem.getBoundingClientRect().left; | |
} | |
}, | |
refresh() { | |
if (this.$refs.elem) { | |
this.getStaticData(); | |
this.setPosition(); | |
} | |
}, | |
printError(msg) { | |
console.error(msg); | |
} | |
}, | |
mounted() { | |
this.isComponentExists = true; | |
if (typeof window === 'undefined' || typeof document === 'undefined') { | |
return this.printError( | |
'[VueSlideBar error]: window or document is undefined, can not be initialization.' | |
); | |
} | |
this.$nextTick(() => { | |
if (this.isComponentExists) { | |
this.getStaticData(); | |
this.setValue(this.limitValue(this.modelValue), 0); | |
this.bindEvents(); | |
} | |
}); | |
}, | |
beforeUnmount() { | |
this.isComponentExists = false; | |
this.unbindEvents(); | |
} | |
}; | |
</script> | |
<style scoped> | |
.vue-slide-bar-component { | |
position: relative; | |
box-sizing: border-box; | |
user-select: none; | |
} | |
.vue-slide-bar { | |
position: relative; | |
display: block; | |
border-radius: 15px; | |
background-color: #d8d8d8; | |
cursor: pointer; | |
} | |
.vue-slide-bar::after { | |
content: ""; | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 2; | |
} | |
.vue-slide-bar-process { | |
position: absolute; | |
border-radius: 15px; | |
background-color: #1066fd; | |
transition: all 0s; | |
z-index: 1; | |
width: 0; | |
height: 100%; | |
top: 0; | |
left: 0; | |
will-change: width; | |
} | |
.vue-slide-bar-tooltip-container { | |
position: absolute; | |
transition: all 0s; | |
will-change: transform; | |
cursor: pointer; | |
z-index: 3; | |
left: 0; | |
top: -16px; | |
} | |
.vue-slide-bar-tooltip-wrap { | |
/* display: none; */ | |
position: absolute; | |
z-index: 9; | |
width: 100%; | |
height: 100%; | |
display: block !important; | |
} | |
.vue-slide-bar-tooltip-top { | |
top: -12px; | |
left: 40%; | |
transform: translate(-50%, -100%); | |
} | |
.vue-slide-bar-tooltip { | |
position: relative; | |
font-size: 14px; | |
white-space: nowrap; | |
padding: 2px 5px; | |
min-width: 20px; | |
text-align: center; | |
color: #fff; | |
border-radius: 5px; | |
border: 1px solid #1066fd; | |
background-color: #1066fd; | |
} | |
.vue-slide-bar-tooltip::before { | |
content: ""; | |
position: absolute; | |
bottom: -10px; | |
left: 50%; | |
width: 0; | |
height: 0; | |
border: 5px solid transparent; | |
border-top-color: inherit; | |
transform: translate(-50%, 0); | |
} | |
.vue-slide-bar-range { | |
display: flex; | |
padding: 5px 0; | |
justify-content: space-between; | |
} | |
.vue-slide-bar-separate { | |
position: relative; | |
width: 2px; | |
background-color: #9e9e9e; | |
height: 5px; | |
cursor: pointer; | |
} | |
.vue-slide-bar-separate-text { | |
text-align: center; | |
position: absolute; | |
white-space: nowrap; | |
transform: translate(-50%, 0); | |
top: 6px; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment