256 lines
6.6 KiB
Vue
256 lines
6.6 KiB
Vue
|
<template>
|
|||
|
<view
|
|||
|
class="tn-slider-class tn-slider"
|
|||
|
:class="{'tn-slider--disabled': disabled}"
|
|||
|
:style="{
|
|||
|
backgroundColor: inactiveColor
|
|||
|
}"
|
|||
|
@tap="click"
|
|||
|
>
|
|||
|
<!-- slider滑动线 -->
|
|||
|
<view
|
|||
|
class="tn-slider__gap"
|
|||
|
:style="[
|
|||
|
barStyle,
|
|||
|
{
|
|||
|
height: this.$tn.string.getLengthUnitValue(lineHeight),
|
|||
|
backgroundColor: activeColor
|
|||
|
}
|
|||
|
]"
|
|||
|
>
|
|||
|
<!-- slider滑块 -->
|
|||
|
<view
|
|||
|
class="tn-slider__button-wrap"
|
|||
|
@touchstart="touchStart"
|
|||
|
@touchmove="touchMove"
|
|||
|
@touchend="touchEnd"
|
|||
|
@touchcancel="touchEnd"
|
|||
|
>
|
|||
|
<view v-if="$slots.default || $slots.$default">
|
|||
|
<slot></slot>
|
|||
|
</view>
|
|||
|
<view
|
|||
|
v-else
|
|||
|
class="tn-slider__button"
|
|||
|
:style="[blockStyle, {
|
|||
|
height: this.$tn.string.getLengthUnitValue(blockWidth),
|
|||
|
width: this.$tn.string.getLengthUnitValue(blockWidth),
|
|||
|
backgroundColor: blockColor
|
|||
|
}]"
|
|||
|
></view>
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script>
|
|||
|
export default {
|
|||
|
name: 'tn-slider',
|
|||
|
props: {
|
|||
|
// 进度值
|
|||
|
value: {
|
|||
|
type: [Number, String],
|
|||
|
default: 0
|
|||
|
},
|
|||
|
// 最小值
|
|||
|
min: {
|
|||
|
type: Number,
|
|||
|
default: 0
|
|||
|
},
|
|||
|
// 最大值
|
|||
|
max: {
|
|||
|
type: Number,
|
|||
|
default: 100
|
|||
|
},
|
|||
|
// 步进值
|
|||
|
step: {
|
|||
|
type: Number,
|
|||
|
default: 1
|
|||
|
},
|
|||
|
// 禁用
|
|||
|
disabled: {
|
|||
|
type: Boolean,
|
|||
|
default: false
|
|||
|
},
|
|||
|
// 滑块宽度
|
|||
|
blockWidth: {
|
|||
|
type: Number,
|
|||
|
default: 30
|
|||
|
},
|
|||
|
// 滑动条高度
|
|||
|
lineHeight: {
|
|||
|
type: Number,
|
|||
|
default: 8
|
|||
|
},
|
|||
|
// 滑动条激活的颜色
|
|||
|
activeColor: {
|
|||
|
type: String,
|
|||
|
default: '#01BEFF'
|
|||
|
},
|
|||
|
// 滑动条未被激活的颜色
|
|||
|
inactiveColor: {
|
|||
|
type: String,
|
|||
|
default: '#E6E6E6'
|
|||
|
},
|
|||
|
// 滑块的颜色
|
|||
|
blockColor: {
|
|||
|
type: String,
|
|||
|
default: '#FFFFFF'
|
|||
|
},
|
|||
|
// 自定义滑块的样式
|
|||
|
blockStyle: {
|
|||
|
type: Object,
|
|||
|
default() {
|
|||
|
return {}
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
data() {
|
|||
|
return {
|
|||
|
startX: 0,
|
|||
|
status: 'end',
|
|||
|
newValue: 0,
|
|||
|
distanceX: 0,
|
|||
|
startValue: 0,
|
|||
|
barStyle: {},
|
|||
|
sliderRect: {
|
|||
|
left: 0,
|
|||
|
width: 0
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
watch: {
|
|||
|
value(val) {
|
|||
|
// 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发
|
|||
|
if (this.status === 'end') this.updateValue(val, false)
|
|||
|
}
|
|||
|
},
|
|||
|
created() {
|
|||
|
this.updateValue(this.value, false)
|
|||
|
},
|
|||
|
mounted() {
|
|||
|
this._tGetRect('.tn-slider').then(res => {
|
|||
|
this.sliderRect = res
|
|||
|
})
|
|||
|
},
|
|||
|
methods: {
|
|||
|
// 开始滑动
|
|||
|
touchStart(event) {
|
|||
|
if (this.disabled) return
|
|||
|
if (!event.changedTouches[0]) return
|
|||
|
|
|||
|
this.startX = 0
|
|||
|
// 触摸点
|
|||
|
this.startX = event.changedTouches[0].pageX
|
|||
|
this.startValue = this.format(this.value)
|
|||
|
|
|||
|
// 标识当前开始触摸
|
|||
|
this.status = 'start'
|
|||
|
},
|
|||
|
// 滑动移动中
|
|||
|
touchMove(event) {
|
|||
|
if (this.disabled) return
|
|||
|
if (!event.changedTouches[0]) return
|
|||
|
|
|||
|
// 连续触摸的过程会一直触发本方法,但只有手指触发且移动了才被认为是拖动了,才发出事件
|
|||
|
// 触摸后第一次移动已经将status设置为moving状态,故触摸第二次移动不会触发本事件
|
|||
|
if (this.status === 'start') this.$emit('start')
|
|||
|
let movePageX = event.changedTouches[0].pageX
|
|||
|
// 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
|
|||
|
this.distanceX = movePageX - this.sliderRect.left
|
|||
|
// 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,不能用此更新视图
|
|||
|
// 否则造成通信阻塞,需要每改变一个step值时修改一次视图
|
|||
|
this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + this.min
|
|||
|
this.status = 'moving'
|
|||
|
this.$emit('moving')
|
|||
|
|
|||
|
this.updateValue(this.newValue, true)
|
|||
|
},
|
|||
|
// 滑动结束
|
|||
|
touchEnd() {
|
|||
|
if(this.disabled) return
|
|||
|
if (this.status === 'moving') {
|
|||
|
this.updateValue(this.newValue, false)
|
|||
|
this.$emit('end')
|
|||
|
}
|
|||
|
this.status = 'end'
|
|||
|
},
|
|||
|
// 更新数值
|
|||
|
updateValue(value, drag) {
|
|||
|
// 去掉小数部分,对step进行步进处理
|
|||
|
value = this.format(value)
|
|||
|
const width = Math.round((value - this.min) / (this.max - this.min) * 100)
|
|||
|
// 不允许滑动的距离小于0和超过100
|
|||
|
if (width < 0 || width > 100) return
|
|||
|
// 设置移动的百分比
|
|||
|
let barStyle = {
|
|||
|
width: width + '%'
|
|||
|
}
|
|||
|
// 移动期间取消动画
|
|||
|
if (drag === true) {
|
|||
|
barStyle.transition = 'none'
|
|||
|
} else {
|
|||
|
// 非移动期间,删掉对过渡为空的声明,让css中的声明起效
|
|||
|
delete barStyle.transition
|
|||
|
}
|
|||
|
|
|||
|
// 修改value值
|
|||
|
this.$emit('input', value)
|
|||
|
this.barStyle = barStyle
|
|||
|
},
|
|||
|
// 点击事件
|
|||
|
click(event) {
|
|||
|
if (this.disabled) return
|
|||
|
// 直接点击的情况,计算方式和touchMove方法一致
|
|||
|
const value = (((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * (this.max - this.min)) + this.min
|
|||
|
this.updateValue(value, false)
|
|||
|
},
|
|||
|
// 格式化滑动的值
|
|||
|
format(value) {
|
|||
|
return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style lang="scss" scoped>
|
|||
|
.tn-slider {
|
|||
|
width: 100%;
|
|||
|
position: relative;
|
|||
|
border-radius: 1000rpx;
|
|||
|
// 增加点击的范围
|
|||
|
border-width: 20rpx;
|
|||
|
border-style: solid;
|
|||
|
border-color: transparent;
|
|||
|
background-color: $tn-font-holder-color;
|
|||
|
background-clip: content-box;
|
|||
|
|
|||
|
&__gap {
|
|||
|
position: relative;
|
|||
|
border-radius: inherit;
|
|||
|
transition: width 0.2s;
|
|||
|
background-color: #01BEFF;
|
|||
|
}
|
|||
|
|
|||
|
&__button {
|
|||
|
width: 30rpx;
|
|||
|
height: 30rpx;
|
|||
|
border-radius: 50%;
|
|||
|
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
|
|||
|
background-color: #FFFFFF;
|
|||
|
cursor: pointer;
|
|||
|
|
|||
|
&-wrap {
|
|||
|
position: absolute;
|
|||
|
top: 50%;
|
|||
|
right: 0;
|
|||
|
transform: translate3d(50%, -50%, 0);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&--disabled {
|
|||
|
opacity: 0.6;
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|