|
<template> |
|
<div> |
|
<div class="range_container"> |
|
<div class="sliders_control"> |
|
<input |
|
id="fromSlider" |
|
v-model="fromVal" |
|
type="range" |
|
:min="min" |
|
:max="max" |
|
/> |
|
<input |
|
id="toSlider" |
|
v-model="toVal" |
|
type="range" |
|
:style="{ |
|
background: sliderBackgroundGradient, |
|
zIndex: to <= 0 ? 2 : 0, |
|
}" |
|
:min="min" |
|
:max="max" |
|
/> |
|
</div> |
|
</div> |
|
</div> |
|
</template> |
|
|
|
<script> |
|
// https://medium.com/@predragdavidovic10/native-dual-range-slider-html-css-javascript-91e778134816 |
|
// https://codepen.io/predragdavidovic/pen/mdpMoWo |
|
|
|
// TODO: in vue3, we can have a simpler multiple v-model setup instead of emitting events |
|
export default { |
|
props: { |
|
from: { type: Number, default: 0 }, |
|
to: { type: Number, default: 100 }, |
|
min: { type: Number, default: 0 }, |
|
max: { type: Number, default: 100 }, |
|
sliderColor: { type: String, default: '#C6C6C6' }, |
|
rangeColor: { type: String, default: '#338CFF' }, |
|
}, |
|
data() { |
|
return { |
|
fromVal: null, |
|
toVal: null, |
|
} |
|
}, |
|
computed: { |
|
sliderBackgroundGradient() { |
|
const rangeDistance = this.max - this.min |
|
const fromPosition = this.fromVal - this.min |
|
const toPosition = this.toVal - this.min |
|
|
|
return `linear-gradient( |
|
to right, |
|
${this.sliderColor} 0%, |
|
${this.sliderColor} ${(fromPosition / rangeDistance) * 100}%, |
|
${this.rangeColor} ${(fromPosition / rangeDistance) * 100}%, |
|
${this.rangeColor} ${(toPosition / rangeDistance) * 100}%, |
|
${this.sliderColor} ${(toPosition / rangeDistance) * 100}%, |
|
${this.sliderColor} 100%)` |
|
}, |
|
}, |
|
watch: { |
|
to(t) { |
|
this.toVal = t |
|
}, |
|
from(f) { |
|
this.fromVal = f |
|
}, |
|
fromVal(newFrom) { |
|
if (Number(newFrom) > Number(this.toVal)) { |
|
this.fromVal = Number(this.toVal) |
|
} |
|
this.fromVal = Number(this.fromVal) |
|
this.$emit('fromChange', this.fromVal) |
|
}, |
|
toVal(newTo) { |
|
if (Number(newTo) < Number(this.fromVal)) { |
|
this.toVal = Number(this.fromVal) |
|
} |
|
this.toVal = Number(this.toVal) |
|
this.$emit('toChange', this.toVal) |
|
}, |
|
}, |
|
created() { |
|
this.fromVal = Math.max(this.from, this.min) |
|
this.toVal = Math.min(this.to, this.max) |
|
}, |
|
} |
|
</script> |
|
|
|
<style scoped> |
|
.range_container { |
|
height: 42px; |
|
} |
|
|
|
.sliders_control { |
|
position: relative; |
|
top: 20px; |
|
min-height: 20px; |
|
} |
|
|
|
input[type='range']::-webkit-slider-thumb { |
|
-webkit-appearance: none; |
|
pointer-events: all; |
|
width: 24px; |
|
height: 24px; |
|
background-color: #fff; |
|
border-radius: 50%; |
|
box-shadow: 0 0 0 1px #c6c6c6; |
|
cursor: pointer; |
|
} |
|
|
|
input[type='range']::-moz-range-thumb { |
|
-webkit-appearance: none; |
|
pointer-events: all; |
|
width: 24px; |
|
height: 24px; |
|
background-color: #fff; |
|
border-radius: 50%; |
|
box-shadow: 0 0 0 1px #c6c6c6; |
|
cursor: pointer; |
|
} |
|
|
|
input[type='range']::-webkit-slider-thumb:hover { |
|
background: #f7f7f7; |
|
} |
|
|
|
input[type='range']::-webkit-slider-thumb:active { |
|
box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe; |
|
-webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe; |
|
} |
|
|
|
input[type='range'] { |
|
-webkit-appearance: none; |
|
appearance: none; |
|
height: 3px; |
|
width: 100%; |
|
position: absolute; |
|
background-color: #c6c6c6; |
|
pointer-events: none; |
|
} |
|
|
|
#fromSlider { |
|
height: 0; |
|
z-index: 1; |
|
} |
|
</style> |