325 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
| <template>
 | |
|   <div class="md-slider" :class="{'is-disabled': disabled}">
 | |
|     <template v-if="range">
 | |
|       <div class="md-slider-bar" :style="barStyle"></div>
 | |
|       <div class="md-slider-handle is-lower"
 | |
|         :data-hint="format(values[0])"
 | |
|         :class="{
 | |
|           'is-active': isDragging && !isDragingUpper
 | |
|         }"
 | |
|         :style="{'left': lowerHandlePosition + '%'}">
 | |
|         <span
 | |
|           @mousedown="$_startLowerDrag"
 | |
|           @touchstart="$_startLowerDrag"
 | |
|         ></span>
 | |
|       </div>
 | |
|       <div class="md-slider-handle is-higher"
 | |
|         :data-hint="format(values[1])"
 | |
|         :class="{
 | |
|           'is-active': isDragging && isDragingUpper
 | |
|         }"
 | |
|         :style="{'left': upperHandlePosition + '%'}">
 | |
|         <span
 | |
|           @mousedown="$_startUpperDrag"
 | |
|           @touchstart="$_startUpperDrag"
 | |
|         ></span>
 | |
|       </div>
 | |
|     </template>
 | |
|     <template v-else>
 | |
|       <div class="md-slider-bar" :style="barStyle"></div>
 | |
|       <div class="md-slider-handle"
 | |
|         :data-hint="format(values[0])"
 | |
|         :class="{
 | |
|           'is-active': isDragging
 | |
|         }"
 | |
|         :style="{'left': lowerHandlePosition + '%'}">
 | |
|         <span
 | |
|           @mousedown="$_startLowerDrag"
 | |
|           @touchstart="$_startLowerDrag"
 | |
|         ></span>
 | |
|       </div>
 | |
|     </template>
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| export default {
 | |
|   name: 'md-slider',
 | |
| 
 | |
|   props: {
 | |
|     value: {
 | |
|       type: [Array, Number],
 | |
|       default: 0,
 | |
|     },
 | |
|     min: {
 | |
|       type: Number,
 | |
|       default: 0,
 | |
|     },
 | |
|     max: {
 | |
|       type: Number,
 | |
|       default: 100,
 | |
|     },
 | |
|     step: {
 | |
|       type: Number,
 | |
|       default: 1,
 | |
|     },
 | |
|     range: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|     format: {
 | |
|       type: Function,
 | |
|       default(val) {
 | |
|         return val
 | |
|       },
 | |
|     },
 | |
|     disabled: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   data() {
 | |
|     return {
 | |
|       isDragging: false,
 | |
|       isDragingUpper: false,
 | |
|       values: [this.min, this.max],
 | |
|       startDragMousePos: 0,
 | |
|       startVal: 0,
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   watch: {
 | |
|     value: {
 | |
|       immediate: true,
 | |
|       handler(val) {
 | |
|         if (
 | |
|           (Array.isArray(val) && (val[0] !== this.values[0] || val[1] !== this.values[1])) ||
 | |
|           val !== this.values[0]
 | |
|         ) {
 | |
|           this.$_updateValue(val)
 | |
|         }
 | |
|       },
 | |
|     },
 | |
|     disabled(newVal) {
 | |
|       if (!newVal) {
 | |
|         this.$_stopDrag()
 | |
|       }
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   computed: {
 | |
|     lowerHandlePosition() {
 | |
|       return (this.values[0] - this.min) / (this.max - this.min) * 100
 | |
|     },
 | |
|     upperHandlePosition() {
 | |
|       return (this.values[1] - this.min) / (this.max - this.min) * 100
 | |
|     },
 | |
|     barStyle() {
 | |
|       if (this.range) {
 | |
|         return {
 | |
|           width: (this.values[1] - this.values[0]) / (this.max - this.min) * 100 + '%',
 | |
|           left: this.lowerHandlePosition + '%',
 | |
|         }
 | |
|       } else {
 | |
|         return {
 | |
|           width: this.values[0] / (this.max - this.min) * 100 + '%',
 | |
|         }
 | |
|       }
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   methods: {
 | |
|     $_updateValue(newVal) {
 | |
|       let newValues = []
 | |
| 
 | |
|       if (Array.isArray(newVal)) {
 | |
|         newValues = [newVal[0], newVal[1]]
 | |
|       } else {
 | |
|         newValues[0] = newVal
 | |
|       }
 | |
| 
 | |
|       if (typeof newValues[0] !== 'number') {
 | |
|         newValues[0] = this.values[0]
 | |
|       } else {
 | |
|         newValues[0] = Math.round((newValues[0] - this.min) / this.step) * this.step + this.min
 | |
|       }
 | |
| 
 | |
|       if (typeof newValues[1] !== 'number') {
 | |
|         newValues[1] = this.values[1]
 | |
|       } else {
 | |
|         newValues[1] = Math.round((newValues[1] - this.min) / this.step) * this.step + this.min
 | |
|       }
 | |
| 
 | |
|       // value boundary adjust
 | |
|       if (newValues[0] < this.min) {
 | |
|         newValues[0] = this.min
 | |
|       }
 | |
|       if (newValues[1] > this.max) {
 | |
|         newValues[1] = this.max
 | |
|       }
 | |
|       if (newValues[0] > newValues[1]) {
 | |
|         if (newValues[0] === this.values[0]) {
 | |
|           newValues[1] = newValues[0]
 | |
|         } else {
 | |
|           newValues[0] = newValues[1]
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (this.values[0] === newValues[0] && this.values[1] === newValues[1]) {
 | |
|         return
 | |
|       }
 | |
| 
 | |
|       this.values = newValues
 | |
| 
 | |
|       if (this.range) {
 | |
|         this.$emit('input', this.values)
 | |
|       } else {
 | |
|         this.$emit('input', this.values[0])
 | |
|       }
 | |
|     },
 | |
|     $_startLowerDrag(e) {
 | |
|       if (this.disabled) {
 | |
|         return
 | |
|       }
 | |
|       e.preventDefault()
 | |
|       e.stopPropagation()
 | |
|       e = e.changedTouches ? e.changedTouches[0] : e
 | |
|       this.startDragMousePos = e.pageX
 | |
|       this.startVal = this.values[0]
 | |
|       this.isDragingUpper = false
 | |
|       this.isDragging = true
 | |
|       window.addEventListener('mousemove', this.$_onDrag)
 | |
|       window.addEventListener('touchmove', this.$_onDrag)
 | |
|       window.addEventListener('mouseup', this.$_onUp)
 | |
|       window.addEventListener('touchend', this.$_onUp)
 | |
|     },
 | |
|     $_startUpperDrag(e) {
 | |
|       if (this.disabled) {
 | |
|         return
 | |
|       }
 | |
|       e.preventDefault()
 | |
|       e.stopPropagation()
 | |
|       e = e.changedTouches ? e.changedTouches[0] : e
 | |
|       this.startDragMousePos = e.pageX
 | |
|       this.startVal = this.values[1]
 | |
|       this.isDragingUpper = true
 | |
|       this.isDragging = true
 | |
|       window.addEventListener('mousemove', this.$_onDrag)
 | |
|       window.addEventListener('touchmove', this.$_onDrag)
 | |
|       window.addEventListener('mouseup', this.$_onUp)
 | |
|       window.addEventListener('touchend', this.$_onUp)
 | |
|     },
 | |
|     $_onDrag(e) {
 | |
|       if (this.disabled) {
 | |
|         return
 | |
|       }
 | |
|       e.preventDefault()
 | |
|       e.stopPropagation()
 | |
|       if (!this.isDragging) {
 | |
|         return
 | |
|       }
 | |
|       e = e.changedTouches ? e.changedTouches[0] : e
 | |
|       window.requestAnimationFrame(() => {
 | |
|         let diff = (e.pageX - this.startDragMousePos) / this.$el.offsetWidth * (this.max - this.min)
 | |
|         let nextVal = this.startVal + diff
 | |
|         if (this.isDragging) {
 | |
|           if (this.isDragingUpper) {
 | |
|             this.$_updateValue([null, nextVal])
 | |
|           } else {
 | |
|             this.$_updateValue([nextVal, null])
 | |
|           }
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     $_onUp(e) {
 | |
|       e.preventDefault()
 | |
|       e.stopPropagation()
 | |
|       this.$_stopDrag()
 | |
|     },
 | |
|     $_stopDrag() {
 | |
|       this.isDragging = false
 | |
|       this.isDragingUpper = false
 | |
|       window.removeEventListener('mousemove', this.$_onDrag)
 | |
|       window.removeEventListener('touchmove', this.$_onDrag)
 | |
|       window.removeEventListener('mouseup', this.$_onUp)
 | |
|       window.removeEventListener('touchend', this.$_onUp)
 | |
|     },
 | |
|   },
 | |
| }
 | |
| 
 | |
| </script>
 | |
| 
 | |
| <style lang="stylus">
 | |
| .md-slider
 | |
|   position relative
 | |
|   width 100%
 | |
|   height 60px
 | |
|   &::before
 | |
|     content ''
 | |
|     position absolute
 | |
|     top 28px
 | |
|     left 0
 | |
|     right 0
 | |
|     height 4px
 | |
|     border-radius 2px
 | |
|     background-color slider-bg-base
 | |
|   &.is-disabled
 | |
|     .md-slider-bar
 | |
|       opacity 0.35
 | |
|     .md-slider-handle span
 | |
|       cursor: not-allowed
 | |
| 
 | |
| .md-slider-bar
 | |
|   position absolute
 | |
|   left 0
 | |
|   top 28px
 | |
|   height 4px
 | |
|   background-color slider-bg-tap
 | |
|   border-radius 2px
 | |
|   z-index 5
 | |
| 
 | |
| .md-slider-handle
 | |
|   position absolute
 | |
|   top 10px
 | |
|   left 0
 | |
|   margin-left -20px
 | |
|   z-index 15
 | |
|   overflow visible
 | |
|   &::after
 | |
|     content attr(data-hint)
 | |
|     color tip-color
 | |
|     position absolute
 | |
|     pointer-events none
 | |
|     opacity 0
 | |
|     visibility hidden
 | |
|     z-index 15
 | |
|     font-size font-minor-normal
 | |
|     line-height 1.25
 | |
|     padding 8px 16px
 | |
|     border-radius radius-normal
 | |
|     background-color tip-fill
 | |
|     white-space nowrap
 | |
|     left 50%
 | |
|     bottom 100%
 | |
|     margin-bottom 20px
 | |
|     transform translateX(-50%)
 | |
| 
 | |
|   &:hover::after,
 | |
|   &:active::after
 | |
|     opacity 1
 | |
|     visibility visible
 | |
| 
 | |
|   &.is-higher
 | |
|     z-index 20
 | |
|   &.is-active span
 | |
|     transform scale(1.3)
 | |
|   span
 | |
|     display block
 | |
|     cursor pointer
 | |
|     width 40px
 | |
|     height 40px
 | |
|     background-color slider-handle-bg
 | |
|     border-radius 50%
 | |
|     box-shadow 0 1px 2px rgba(0, 0, 0, 0.2)
 | |
|     transition transform 200ms
 | |
| </style>
 |