连云港陪玩陪聊
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.
chunwan/components/sliderRange.vue

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>