You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
9.0 KiB
382 lines
9.0 KiB
3 months ago
|
<template>
|
||
|
<view
|
||
|
class="slider-range"
|
||
|
:class="{ disabled: disabled }"
|
||
|
:style="{ paddingLeft: blockSize / 2 + 'px', paddingRight: blockSize / 2 + 'px' }"
|
||
|
>
|
||
|
<view class="slider-range-inner" :style="{ height: height + 'px' }">
|
||
|
<view
|
||
|
class="slider-bar"
|
||
|
:style="{
|
||
|
height: barHeight + 'px',
|
||
|
}"
|
||
|
>
|
||
|
<!-- 背景条 -->
|
||
|
<view
|
||
|
class="slider-bar-bg"
|
||
|
:style="{
|
||
|
backgroundColor: backgroundColor,
|
||
|
}"
|
||
|
></view>
|
||
|
|
||
|
<!-- 滑块实际区间 -->
|
||
|
<view
|
||
|
class="slider-bar-inner"
|
||
|
:style="{
|
||
|
width: ((values[1] - values[0]) / (max - min)) * 100 + '%',
|
||
|
left: lowerHandlePosition + '%',
|
||
|
backgroundColor: activeColor,
|
||
|
}"
|
||
|
></view>
|
||
|
</view>
|
||
|
|
||
|
<!-- 滑动块-左 -->
|
||
|
<view
|
||
|
class="slider-handle-block"
|
||
|
:class="{ decoration: decorationVisible }"
|
||
|
:style="{
|
||
|
backgroundColor: blockColor,
|
||
|
width: blockSize + 'px',
|
||
|
height: blockSize + 'px',
|
||
|
left: lowerHandlePosition + '%',
|
||
|
}"
|
||
|
@touchstart="_onTouchStart"
|
||
|
@touchmove="_onBlockTouchMove"
|
||
|
@touchend="_onBlockTouchEnd"
|
||
|
data-tag="lowerBlock"
|
||
|
>L</view>
|
||
|
|
||
|
<!-- 滑动块-右 -->
|
||
|
<view
|
||
|
class="slider-handle-block"
|
||
|
:class="{ decoration: decorationVisible }"
|
||
|
:style="{
|
||
|
backgroundColor: blockColor,
|
||
|
width: blockSize + 'px',
|
||
|
height: blockSize + 'px',
|
||
|
left: higherHandlePosition + '%',
|
||
|
}"
|
||
|
@touchstart="_onTouchStart"
|
||
|
@touchmove="_onBlockTouchMove"
|
||
|
@touchend="_onBlockTouchEnd"
|
||
|
data-tag="higherBlock"
|
||
|
>R</view>
|
||
|
|
||
|
<!-- 滑块值提示 -->
|
||
|
<view v-if="tipVisible" class="range-tip" :style="lowerTipStyle">{{ format(values[0]) }}</view>
|
||
|
<view v-if="tipVisible" class="range-tip" :style="higherTipStyle">{{ format(values[1]) }}</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</template>
|
||
|
<script>
|
||
|
export default {
|
||
|
components: {},
|
||
|
props: {
|
||
|
//滑块区间当前取值
|
||
|
value: {
|
||
|
type: Array,
|
||
|
default: function() {
|
||
|
return [0, 100]
|
||
|
},
|
||
|
},
|
||
|
//最小值
|
||
|
min: {
|
||
|
type: Number,
|
||
|
default: 0,
|
||
|
},
|
||
|
//最大值
|
||
|
max: {
|
||
|
type: Number,
|
||
|
default: 100,
|
||
|
},
|
||
|
step: {
|
||
|
type: Number,
|
||
|
default: 1,
|
||
|
},
|
||
|
format: {
|
||
|
type: Function,
|
||
|
default: function(val) {
|
||
|
return val
|
||
|
},
|
||
|
},
|
||
|
disabled: {
|
||
|
type: Boolean,
|
||
|
default: false,
|
||
|
},
|
||
|
//滑块容器高度
|
||
|
height: {
|
||
|
height: Number,
|
||
|
default: 50,
|
||
|
},
|
||
|
//区间进度条高度
|
||
|
barHeight: {
|
||
|
type: Number,
|
||
|
default: 5,
|
||
|
},
|
||
|
//背景条颜色
|
||
|
backgroundColor: {
|
||
|
type: String,
|
||
|
default: '#e9e9e9',
|
||
|
},
|
||
|
//已选择的颜色
|
||
|
activeColor: {
|
||
|
type: String,
|
||
|
default: '#1aad19',
|
||
|
},
|
||
|
//滑块大小
|
||
|
blockSize: {
|
||
|
type: Number,
|
||
|
default: 20,
|
||
|
},
|
||
|
blockColor: {
|
||
|
type: String,
|
||
|
default: '#fff',
|
||
|
},
|
||
|
tipVisible: {
|
||
|
type: Boolean,
|
||
|
default: true,
|
||
|
},
|
||
|
decorationVisible: {
|
||
|
type: Boolean,
|
||
|
default: false,
|
||
|
},
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
values: [this.min, this.max],
|
||
|
startDragPos: 0, // 开始拖动时的坐标位置
|
||
|
startVal: 0, //开始拖动时较小点的值
|
||
|
}
|
||
|
},
|
||
|
computed: {
|
||
|
// 较小点滑块的坐标
|
||
|
lowerHandlePosition() {
|
||
|
return ((this.values[0] - this.min) / (this.max - this.min)) * 100
|
||
|
},
|
||
|
// 较大点滑块的坐标
|
||
|
higherHandlePosition() {
|
||
|
return ((this.values[1] - this.min) / (this.max - this.min)) * 100
|
||
|
},
|
||
|
lowerTipStyle() {
|
||
|
if (this.lowerHandlePosition < 90) {
|
||
|
return `left: ${this.lowerHandlePosition}%;`
|
||
|
}
|
||
|
return `right: ${100 - this.lowerHandlePosition}%;transform: translate(50%, -100%);`
|
||
|
},
|
||
|
higherTipStyle() {
|
||
|
if (this.higherHandlePosition < 90) {
|
||
|
return `left: ${this.higherHandlePosition}%;`
|
||
|
}
|
||
|
return `right: ${100 - this.higherHandlePosition}%;transform: translate(50%, -100%);`
|
||
|
},
|
||
|
},
|
||
|
created: function() {},
|
||
|
onLoad: function(option) {},
|
||
|
watch: {
|
||
|
//滑块当前值
|
||
|
value: {
|
||
|
immediate: true,
|
||
|
handler(newVal, oldVal) {
|
||
|
if (this._isValuesValid(newVal) && (newVal[0] !== this.values[0] || newVal[1] !== this.values[1])) {
|
||
|
this._updateValue(newVal)
|
||
|
}
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
methods: {
|
||
|
_updateValue(newVal) {
|
||
|
// 步长大于区间差,或者区间最大值和最小值相等情况
|
||
|
if (this.step >= this.max - this.min) {
|
||
|
throw new RangeError('Invalid slider step or slider range')
|
||
|
}
|
||
|
|
||
|
let newValues = []
|
||
|
if (Array.isArray(newVal)) {
|
||
|
newValues = [newVal[0], newVal[1]]
|
||
|
}
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// 新值与原值相等,不做处理
|
||
|
if (this.values[0] === newValues[0] && this.values[1] === newValues[1]) {
|
||
|
return
|
||
|
}
|
||
|
// 左侧滑块值小于最小值时,设置为最小值
|
||
|
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] + this.step
|
||
|
} else {
|
||
|
// 右侧未动, 左侧滑块滑到右侧之右
|
||
|
newValues[0] = newValues[1] - this.step
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.values = newValues
|
||
|
this.$emit('change', this.values)
|
||
|
},
|
||
|
_onTouchStart: function(event) {
|
||
|
if (this.disabled) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
this.isDragging = true
|
||
|
let tag = event.target.dataset.tag
|
||
|
|
||
|
//兼容h5平台及某版本微信
|
||
|
let e = event.changedTouches ? event.changedTouches[0] : event
|
||
|
this.startDragPos = e.pageX
|
||
|
|
||
|
this.startVal = tag === 'lowerBlock' ? this.values[0] : this.values[1]
|
||
|
},
|
||
|
_onBlockTouchMove: function(e) {
|
||
|
if (this.disabled) {
|
||
|
return
|
||
|
}
|
||
|
this._onDrag(e)
|
||
|
},
|
||
|
_onBlockTouchEnd: function(e) {
|
||
|
if (this.disabled) {
|
||
|
return
|
||
|
}
|
||
|
this.isDragging = false
|
||
|
this._onDrag(e)
|
||
|
},
|
||
|
_onDrag(event) {
|
||
|
if (!this.isDragging) {
|
||
|
return
|
||
|
}
|
||
|
let view = uni
|
||
|
.createSelectorQuery()
|
||
|
.in(this)
|
||
|
.select('.slider-range-inner')
|
||
|
|
||
|
view
|
||
|
.boundingClientRect(data => {
|
||
|
let sliderWidth = data.width
|
||
|
const tag = event.target.dataset.tag
|
||
|
let e = event.changedTouches ? event.changedTouches[0] : event
|
||
|
let diff = ((e.pageX - this.startDragPos) / sliderWidth) * (this.max - this.min)
|
||
|
let nextVal = this.startVal + diff
|
||
|
|
||
|
if (tag === 'lowerBlock') {
|
||
|
this._updateValue([nextVal, null])
|
||
|
} else {
|
||
|
this._updateValue([null, nextVal])
|
||
|
}
|
||
|
})
|
||
|
.exec()
|
||
|
},
|
||
|
_isValuesValid: function(values) {
|
||
|
return Array.isArray(values) && values.length == 2
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<style scoped>
|
||
|
.slider-range {
|
||
|
width: 100%;
|
||
|
position: relative;
|
||
|
padding-top: 40rpx;
|
||
|
}
|
||
|
|
||
|
.slider-range-inner {
|
||
|
position: relative;
|
||
|
width: 100%;
|
||
|
}
|
||
|
|
||
|
.slider-range.disabled .slider-bar-inner {
|
||
|
opacity: 0.35;
|
||
|
}
|
||
|
|
||
|
.slider-range.disabled .slider-handle-block {
|
||
|
cursor: not-allowed;
|
||
|
}
|
||
|
|
||
|
.slider-bar {
|
||
|
position: absolute;
|
||
|
top: 50%;
|
||
|
left: 0;
|
||
|
right: 0;
|
||
|
transform: translateY(-50%);
|
||
|
}
|
||
|
|
||
|
.slider-bar-bg {
|
||
|
position: absolute;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
border-radius: 10000px;
|
||
|
z-index: 10;
|
||
|
}
|
||
|
|
||
|
.slider-bar-inner {
|
||
|
position: absolute;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
border-radius: 10000px;
|
||
|
z-index: 11;
|
||
|
}
|
||
|
|
||
|
.slider-handle-block {
|
||
|
position: absolute;
|
||
|
top: 50%;
|
||
|
transform: translate(-50%, -50%);
|
||
|
border-radius: 50%;
|
||
|
box-shadow: 0 0 3px 2px rgba(227, 229, 241, 0.5);
|
||
|
z-index: 12;
|
||
|
text-align: center;
|
||
|
border: 1px solid #CCCCCC;
|
||
|
}
|
||
|
|
||
|
.slider-handle-block.decoration::before {
|
||
|
position: absolute;
|
||
|
content: '';
|
||
|
width: 6upx;
|
||
|
height: 24upx;
|
||
|
top: 50%;
|
||
|
left: 29%;
|
||
|
transform: translateY(-50%);
|
||
|
background: #eeedf2;
|
||
|
border-radius: 3upx;
|
||
|
z-index: 13;
|
||
|
}
|
||
|
|
||
|
.slider-handle-block.decoration::after {
|
||
|
position: absolute;
|
||
|
content: '';
|
||
|
width: 6upx;
|
||
|
height: 24upx;
|
||
|
top: 50%;
|
||
|
right: 29%;
|
||
|
transform: translateY(-50%);
|
||
|
background: #eeedf2;
|
||
|
border-radius: 3upx;
|
||
|
z-index: 13;
|
||
|
}
|
||
|
|
||
|
.range-tip {
|
||
|
position: absolute;
|
||
|
top: 0;
|
||
|
font-size: 24upx;
|
||
|
color: #666;
|
||
|
transform: translate(-50%, -100%);
|
||
|
}
|
||
|
</style>
|