🐳
Vue.jsでお手製RangeSlider
完成形
それっぽいものを作ってみました。
なぜ実装しようと思ったか
私は普段はReactを使っています。ReactでもComponent外のマウス操作で画面を更新したいときがあります。そんなときはbodyなどに対してaddEventListener
によるイベントを設定し、ref
を用いてDOMからleftやwidthを取得してstateを更新することがあります。
Vue.jsではどんな感じかなーということでやってみました。
実装
<template>
<div class="range_slider" :style="{width: width + 'px'}">
<div class="range_wrapper" ref="range" @mousedown="mousedown">
<div class="range" :style="{'background-color': color}"></div>
</div>
<div class="circle" :style="circleStyle" @mousedown="mousedown"></div>
</div>
</template>
<script>
export default {
name: 'RangeSlider',
props: {
color: {type: String, default: '#eee'},
width: {type: Number, default: 200},
value: {type: Number, default: 0},
max: {type: Number, default: 100},
min: {type: Number, default: 0}
},
data () {
return {
isMoving: false
}
},
methods: {
moveCircle (e) {
const width = this.$refs.range.getBoundingClientRect().width
const rangeLeft = this.$refs.range.getBoundingClientRect().left
const eventLeft = e.clientX
const left = eventLeft - rangeLeft
if (left <= 0) {
this.$emit('update:value', this.min)
}
if (width <= left) {
this.$emit('update:value', this.max)
}
if (left >= 0 && left <= width) {
const ratio = left / width
const value = (this.max - this.min) * ratio + this.min
this.$emit('update:value', Math.round(value))
}
},
mousedown (e) {
this.isMoving = true
this.moveCircle(e)
// mousedownしたタイミングでマウス操作に対するイベントを設定
document.body.addEventListener('mousemove', this.mousemove)
document.body.addEventListener('mouseup', this.mouseup)
},
mousemove (e) {
if (this.isMoving) {
this.moveCircle(e)
}
},
mouseup () {
this.isMoving = false
// ドラッグ操作が終わったタイミングでイベントを削除
document.body.removeEventListener('mousemove', this.mousemove)
document.body.removeEventListener('mouseup', this.mouseup)
}
},
computed: {
circleStyle () {
const ratio = (this.value - this.min) / (this.max - this.min)
const left = this.width * ratio
return {
left: `${left - 10}px`
}
}
}
}
</script>
<style scoped>
.range_slider {
height: 20px;
margin: 0 auto;
position: relative;
display: inline-block;
}
.range_wrapper {
position: relative;
height: 100%;
width: 100%;
}
.range {
height: 4px;
width: 100%;
background-color: #eee;
position: absolute;
top: calc(50% - 2px);
border-radius: 3px;
}
.circle {
width: 0;
height: 0;
top: 0;
border: 10px solid #DDD;
border-radius: 50%;
position: absolute;
}
</style>
補足
Reactと違う点はthis.$refs
を使用する点でしょうか。Reactでもthis.refs
として取得する方法があったと思いますが、Reactでは非推奨になっているはずですね。
ここまで実装して思ったのが、HTML5のinputのrangeを使用しても実装できるかもしれないという点です。inputのrangeの円よりも大きい円を上に被せるような実装をすればマウス操作を気にする必要はないかもしれません。
Discussion