0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue.jsでお手製RangeSlider

Posted at

完成形

それっぽいものを作ってみました。
range_slider.gif

なぜ実装しようと思ったか

私は普段は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の円よりも大きい円を上に被せるような実装をすればマウス操作を気にする必要はないかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?