<template> <div class="el-slider__button-wrapper" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave" @mousedown="onButtonDown" @touchstart="onButtonDown" :class="{ hover: hovering, dragging: dragging }" :style="wrapperStyle" ref="button" tabindex="0" @focus="handleMouseEnter" @blur="handleMouseLeave" @keydown.left="onLeftKeyDown" @keydown.right="onRightKeyDown" @keydown.down.prevent="onLeftKeyDown" @keydown.up.prevent="onRightKeyDown"> <el-tooltip placement="top" ref="tooltip" :popper-class="tooltipClass" :disabled="!showTooltip"> <span slot="content">{{ formatValue }}</span> <div class="el-slider__button" :class="{ hover: hovering, dragging: dragging }"></div> </el-tooltip> </div> </template> <script> // import ElTooltip from 'element-ui/packages/tooltip' export default { name: 'ElSliderButton', components: { // ElTooltip, }, props: { value: { type: Number, default: 0, }, vertical: { type: Boolean, default: false, }, tooltipClass: String, marks: Object, }, data() { return { hovering: false, dragging: false, isClick: false, startX: 0, currentX: 0, startY: 0, currentY: 0, startPosition: 0, newPosition: null, oldValue: this.value, } }, computed: { disabled() { return this.$parent.sliderDisabled }, max() { return this.$parent.max }, min() { return this.$parent.min }, step() { return this.$parent.step }, showTooltip() { return this.$parent.showTooltip }, precision() { return this.$parent.precision }, currentPosition() { if (this.marks) { // 同父组件index中barSize的计算逻辑. 直接从那边抄过来的. 不要在这里改, 在index改完再抄过来. // 注意父组件的 this.firstValue, 这里是 this.value const marks = Object.keys(this.marks).map(Number) // 找出目标值位于marks的那个档位, 即 即将到达哪一个'档' const index = marks.findIndex(i => this.value < i) if (index === -1) return '100%' // 经过后一个档位之后占总进度条的比例 + (当前值超出后一个档位的差值 / 前一个档位与后一个档位的差值) / (进度被marks划分成的档位数量) // 上述公式要除档位数量是因为, 当前值与档位差值占单个档位区间的百分比 !== 单个区间值占总进度条的百分比 const value = (index - 1) / (marks.length - 1) + (this.value - marks[index - 1]) / (marks[index] - marks[index - 1]) / (marks.length - 1) return value > 1 ? '100%' : `${value * 100}%` } else { // 这里是原本的逻辑. 没有传marks的. return `${((this.value - this.min) / (this.max - this.min)) * 100}%` } }, enableFormat() { return this.$parent.formatTooltip instanceof Function }, formatValue() { return ( (this.enableFormat && this.$parent.formatTooltip(this.value)) || this.value ) }, wrapperStyle() { return this.vertical ? { bottom: this.currentPosition } : { left: this.currentPosition } }, }, watch: { dragging(val) { this.$parent.dragging = val }, }, methods: { displayTooltip() { this.$refs.tooltip && (this.$refs.tooltip.showPopper = true) }, hideTooltip() { this.$refs.tooltip && (this.$refs.tooltip.showPopper = false) }, handleMouseEnter() { this.hovering = true this.displayTooltip() }, handleMouseLeave() { this.hovering = false this.hideTooltip() }, onButtonDown(event) { if (this.disabled) return event.preventDefault() this.onDragStart(event) window.addEventListener('mousemove', this.onDragging) window.addEventListener('touchmove', this.onDragging) window.addEventListener('mouseup', this.onDragEnd) window.addEventListener('touchend', this.onDragEnd) window.addEventListener('contextmenu', this.onDragEnd) }, onLeftKeyDown() { if (this.disabled) return this.newPosition = parseFloat(this.currentPosition) - (this.step / (this.max - this.min)) * 100 this.setPosition(this.newPosition) this.$parent.emitChange() }, onRightKeyDown() { if (this.disabled) return this.newPosition = parseFloat(this.currentPosition) + (this.step / (this.max - this.min)) * 100 this.setPosition(this.newPosition) this.$parent.emitChange() }, onDragStart(event) { this.dragging = true this.isClick = true if (event.type === 'touchstart') { event.clientY = event.touches[0].clientY event.clientX = event.touches[0].clientX } if (this.vertical) { this.startY = event.clientY } else { this.startX = event.clientX } this.startPosition = parseFloat(this.currentPosition) this.newPosition = this.startPosition }, onDragging(event) { if (this.dragging) { this.isClick = false this.displayTooltip() this.$parent.resetSize() let diff = 0 if (event.type === 'touchmove') { event.clientY = event.touches[0].clientY event.clientX = event.touches[0].clientX } if (this.vertical) { this.currentY = event.clientY diff = ((this.startY - this.currentY) / this.$parent.sliderSize) * 100 } else { this.currentX = event.clientX diff = ((this.currentX - this.startX) / this.$parent.sliderSize) * 100 } this.newPosition = this.startPosition + diff this.setPosition(this.newPosition) } }, onDragEnd() { if (this.dragging) { /* * 防止在 mouseup 后立即触发 click,导致滑块有几率产生一小段位移 * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上 */ setTimeout(() => { this.dragging = false this.hideTooltip() if (!this.isClick) { this.setPosition(this.newPosition) this.$parent.emitChange() } }, 0) window.removeEventListener('mousemove', this.onDragging) window.removeEventListener('touchmove', this.onDragging) window.removeEventListener('mouseup', this.onDragEnd) window.removeEventListener('touchend', this.onDragEnd) window.removeEventListener('contextmenu', this.onDragEnd) } }, setPosition(newPosition) { if (newPosition === null || isNaN(newPosition)) return if (newPosition < 0) { newPosition = 0 } else if (newPosition > 100) { newPosition = 100 } const lengthPerStep = 100 / ((this.max - this.min) / this.step) const steps = Math.round(newPosition / lengthPerStep) let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min if (this.marks) { // 处理传递marks时的逻辑. 没有传marks的保持原样. // 有marks时无视原计算逻辑. 重新赋值 const marks = Object.keys(this.marks).map((current, index, arr) => { return { value: Number(current), perc: (100 * index) / (arr.length - 1), } }) // 找出目标值位于marks的那个档位, 即 即将到达哪一个'档' if (newPosition === 100) { value = marks[marks.length - 1].value } else { const index = marks.findIndex(item => item.perc > newPosition) // 后一个区间的代表值 + ((区间数量 * 超出后一个区间的百分比值 / 100) * 所处区间的值差) if (index === -1) { // 在最后一个区间 value = marks[marks.length - 2].value + (marks.length - 1) * (((newPosition - marks[marks.length - 2].perc) / 100) * (marks[marks.length - 1].value - marks[marks.length - 2].value)) } else { value = marks[index - 1].value + (marks.length - 1) * (((newPosition - marks[index - 1].perc) / 100) * (marks[index].value - marks[index - 1].value)) } } } value = parseFloat(value.toFixed(this.precision)) this.$emit('input', value) this.$nextTick(() => { this.displayTooltip() this.$refs.tooltip && this.$refs.tooltip.updatePopper() }) if (!this.dragging && this.value !== this.oldValue) { this.oldValue = this.value } }, }, } </script>