Vue2滑动输入条(Slider)

38 篇文章 5 订阅

Vue3滑动输入条(Slider)

组件支持自定义设置:

  • 滑动输入条最小值(min),默认0
  • 滑动输入条最大值(max),默认100
  • 滑动输入条初始默认值(initialValue),当 range 为 false 时,使用 number,否则用 [number, number],在未设置value(v-model)时生效,默认[0, 100]
  • 滑动输入条在页面中的宽度(width),默认600px
  • 是否禁用(disabled),默认false
  • 是否双滑块模式(range),默认false
  • (v-model)设置当前取值(value),当 range 为 false 时,使用 number,否则用 [number, number]

可以通过鼠标拖动滑块来调整最大最小值,或者直接点击输入条上某个位置,直接切换数值

效果图如下:

单滑块模式(:range="false"):

双滑块模式(:range="true"):

①创建滑动输入条组件Slider.vue

<template>
  <div :class="['m-slider', { disabled: disabled }]" ref="slider" :style="`width: ${width}px;`">
    <div class="u-slider-rail" @click.self="onClickPoint"></div>
    <div class="u-slider-track" :class="{trackTransition: transition}" :style="`left: ${left}px; right: auto; width: ${right - left}px;`"></div>
    <div class="u-slider-handle" :class="{handleTransition: transition}" v-if="range" tabindex="0" ref="left" @mousedown="onLeftMouseDown" :style="`left: ${left}px; right: auto; transform: translateX(-50%);`"></div>
    <div class="u-slider-handle" :class="{handleTransition: transition}" tabindex="0" ref="right" @mousedown="onRightMouseDown" :style="`left: ${right}px; right: auto; transform: translateX(-50%);`"></div>
  </div>
</template>
<script>
export default {
  name: 'Slider',
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    min: { // 滑动输入条最小值
      type: Number,
      default: 0
    },
    max: { // 滑动输入条最大值
      type: Number,
      default: 100
    },
    initialValue: { // 滑动输入条初始默认值,当 range 为 false 时,使用 number,否则用 [number, number],在未设置value(v-model)时生效
      type: [Number, Array],
      default: () => {
        return [0, 100]
      }
    },
    width: { // 滑动输入条在页面中的宽度
      type: Number,
      default: 600
    },
    disabled: { // 是否禁用
      type: Boolean,
      default: false
    },
    range: { // 是否双滑块模式
      type: Boolean,
      default: false
    },
    value: { // (v-model)设置当前取值,当 range 为 false 时,使用 number,否则用 [number, number]
      type: [Number, Array],
      default: null
    }
  },
  data () {
    return {
      transition: false,
      timer: null,
      left: 0, // 左滑块距离滑动条左端的距离
      right: 0 // 右滑动距离滑动条左端的距离
    }
  },
  computed: {
    scale () {
      return this.width / (this.max - this.min)
    },
    sliderValue () {
      const high = Math.round(this.right / this.scale + this.min)
      if (this.range) {
        const low = Math.round(this.left / this.scale + this.min)
        return [low, high]
      }
      return high
    }
  },
  watch: {
    sliderValue (to) {
      this.$emit('change', to) // 通知v-model值变化
    }
  },
  created () {
    if (this.range) { // 双滑块模式
      const leftVal = (this.value && (this.value[0] === 0 || this.value[0])) ? this.value[0] : this.initialValue[0]
      this.left = (leftVal - this.min) * this.scale
      const rightVal = (this.value && (this.value[1] === 0 || this.value[1])) ? this.value[1] : this.initialValue[1]
      this.right = (rightVal - this.min) * this.scale
    } else {
      const rightVal = this.value ? this.value : (Array.isArray(this.initialValue) ? 100 : this.initialValue)
      this.right = (rightVal - this.min) * this.scale
    }
  },
  methods: {
    onClickPoint (e) { // 点击滑动条,移动滑块
      if (this.transition) {
        clearTimeout(this.timer)
        this.timer = null
      } else {
        this.transition = true
      }
      this.timer = setTimeout(() => {
        this.transition = false
      }, 300)
      // 元素是absolute时,e.layerX是相对于自身元素左上角的水平位置
      var moveX = e.layerX // 鼠标点击位置距离滑动输入条左端的水平距离
      if (this.range) { // 双滑块模式
        if (moveX <= this.left) {
          this.left = moveX
        } else if (moveX >= this.right) {
          this.right = moveX
        } else {
          if ((moveX - this.left) < (this.right - moveX)) {
            this.left = moveX
          } else {
            this.right = moveX
          }
        }
      } else { // 单滑块模式
        this.right = moveX
      }
    },
    onLeftMouseDown () { // 在滚动条上拖动左滑块
      const leftX = this.$refs.slider.getBoundingClientRect().left // 滑动条左端距离屏幕可视区域左边界的距离
      document.onmousemove = (e) => {
        // e.clientX返回事件被触发时鼠标指针相对于浏览器可视窗口的水平坐标
        var moveX = e.clientX - leftX
        if (moveX < 0) {
          this.left = 0
        } else if (moveX >= 0 && moveX <= this.right) {
          this.left = moveX
        } else { // moveX > this.right
          this.left = this.right
          this.onRightMouseDown()
        }
      }
      document.onmouseup = () => {
        document.onmousemove = null
      }
    },
    onRightMouseDown () { // 在滚动条上拖动右滑块
      const leftX = this.$refs.slider.getBoundingClientRect().left // 滑动条左端距离屏幕可视区域左边界的距离
      document.onmousemove = (e) => {
        // e.clientX返回事件被触发时鼠标指针相对于浏览器可视窗口的水平坐标
        console.log(e.clientX)
        var moveX = e.clientX - leftX
        if (moveX > this.width) {
          this.right = this.width
        } else if (this.left <= moveX && moveX <= this.width) {
          this.right = moveX
        } else { // moveX < this.left
          this.right = this.left
          this.onLeftMouseDown()
        }
      }
      document.onmouseup = () => {
        document.onmousemove = null
      }
    }
  }
}
</script>
<style lang="less" scoped>
@themeColor: #1890FF;
.m-slider {
  display: inline-block;
  height: 4px;
  position: relative;
  z-index: 9;
  touch-action: none; // 禁用元素上的所有手势,使用自己的拖动和缩放api
  .u-slider-rail { // 灰色未选择滑动条背景色
    position: absolute;
    z-index: 99;
    height: 4px;
    width: 100%;
    background: #f5f5f5;
    border-radius: 2px;
    cursor: pointer;
    transition: background .3s;
  }
  .u-slider-track { // 蓝色已选择滑动条背景色
    position: absolute;
    z-index: 99;
    background: #91d5ff;
    border-radius: 4px;
    height: 4px;
    cursor: pointer;
    transition: background .3s;
    pointer-events: none;
  }
  .trackTransition {
    transition: left .2s, width .2s, background .3s;
  }
  &:hover {
    .u-slider-rail { // 灰色未选择滑动条背景色
      background: #E3E3E3;
    }
    .u-slider-track { // 蓝色已选择滑动条背景色
      background: @themeColor;
    }
  }
  .u-slider-handle { // 滑块
    position: absolute;
    z-index: 999;
    width: 12px;
    height: 12px;
    top: -6px;
    background: #fff;
    border: 2px solid #91d5ff;
    border-radius: 50%;
    cursor: pointer;
    transition: border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28);
    &:focus {
      border-color: @themeColor;
      box-shadow: 0 0 0 5px rgba(24, 144, 255, 0.2);
    }
    &:hover {
      border-color: @themeColor;
    }
  }
  .handleTransition {
    transition: left .2s, border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28);
  }
}
.disabled {
  cursor: not-allowed;
  .u-slider-rail {
    pointer-events: none;
  }
  .u-slider-track {
    background: rgba(0, 0, 0, .25);
  }
  .u-slider-handle {
    border-color: rgba(0, 0, 0, .25);
    pointer-events: none;
  }
  &:hover {
    .u-slider-track {
       background: rgba(0, 0, 0, .25);
    }
  }
}
</style>

②在要使用的页面引入:

<div class="m-num">
	<p class="u-num">{{ value[0] }}</p>
	<p class="u-num">{{ value[1] }}</p>
</div>
<NumSlider
    :min="0"
    :max="100"
    :initialValue="[20, 80]"
    :width="1200"
    :disabled="false"
    :range="true"
    v-model="value"
    @change="onChange"
/>
import NumSlider from '@/components/NumSlider'
components: {
    NumSlider
}
data () {
	return {
		value: [20, 80]
	}
}
methods: {
    onChange (val) {
      console.log('change:', val)
    }
}
.m-num {
  min-width: 1200px;
  width: 1200px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  .u-num {
    color: #333;
    font-size: 24px;
  }
}
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值