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.

483 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view
class="u-slider"
:style="[addStyle(customStyle)]"
>
<template v-if="!useNative || isRange">
<view ref="u-slider-inner" class="u-slider-inner" @click="onClick"
@onTouchStart="onTouchStart2($event, 1)" @touchmove="onTouchMove2($event, 1)"
@touchend="onTouchEnd2($event, 1)" @touchcancel="onTouchEnd2($event, 1)"
:class="[disabled ? 'u-slider--disabled' : '']" :style="{
height: (isRange && showValue) ? (getPx(blockSize) + 24) + 'px' : (getPx(blockSize)) + 'px',
}"
>
<view ref="u-slider__base"
class="u-slider__base"
:style="[
{
height: height,
backgroundColor: inactiveColor
}
]"
>
</view>
<view
@click="onClick"
class="u-slider__gap"
:style="[
barStyle,
{
height: height,
marginTop: '-' + height,
backgroundColor: activeColor
}
]"
>
</view>
<view v-if="isRange"
class="u-slider__gap u-slider__gap-0"
:style="[
barStyle0,
{
height: height,
marginTop: '-' + height,
backgroundColor: inactiveColor
}
]"
>
</view>
<text v-if="isRange && showValue"
class="u-slider__show-range-value" :style="{left: (getPx(barStyle0.width) + getPx(blockSize)/2) + 'px'}">
{{ this.rangeValue[0] }}
</text>
<text v-if="isRange && showValue"
class="u-slider__show-range-value" :style="{left: (getPx(barStyle.width) + getPx(blockSize)/2) + 'px'}">
{{ this.rangeValue[1] }}
</text>
<template v-if="isRange">
<view class="u-slider__button-wrap u-slider__button-wrap-0" @touchstart="onTouchStart($event, 0)"
@touchmove="onTouchMove($event, 0)" @touchend="onTouchEnd($event, 0)"
@touchcancel="onTouchEnd($event, 0)" :style="{left: (getPx(barStyle0.width) + getPx(blockSize)/2) + 'px'}">
<slot v-if="$slots.default || $slots.$default"/>
<view v-else class="u-slider__button" :style="[blockStyle, {
height: getPx(blockSize, true),
width: getPx(blockSize, true),
backgroundColor: blockColor
}]"></view>
</view>
</template>
<view class="u-slider__button-wrap" @touchstart="onTouchStart"
@touchmove="onTouchMove" @touchend="onTouchEnd"
@touchcancel="onTouchEnd" :style="{left: (getPx(barStyle.width) + getPx(blockSize)/2) + 'px'}">
<slot v-if="$slots.default || $slots.$default"/>
<view v-else class="u-slider__button" :style="[blockStyle, {
height: getPx(blockSize, true),
width: getPx(blockSize, true),
backgroundColor: blockColor
}]"></view>
</view>
</view>
<view class="u-slider__show-value" v-if="showValue && !isRange">{{ modelValue }}</view>
</template>
<slider
class="u-slider__native"
v-else
:min="min"
:max="max"
:step="step"
:value="modelValue"
:activeColor="activeColor"
:backgroundColor="inactiveColor"
:blockSize="getPx(blockSize)"
:blockColor="blockColor"
:showValue="showValue"
:disabled="disabled"
@changing="changingHandler"
@change="changeHandler"
></slider>
</view>
</template>
<script>
import { props } from './props';
import { mpMixin } from '../../libs/mixin/mpMixin';
import { mixin } from '../../libs/mixin/mixin';
import { addStyle, getPx, sleep } from '../../libs/function/index.js';
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* slider 滑块选择器
* @tutorial https://uview-plus.jiangruyi.com/components/slider.html
* @property {Number | String} value 滑块默认值默认0
* @property {Number | String} min 最小值默认0
* @property {Number | String} max 最大值默认100
* @property {Number | String} step 步长默认1
* @property {Number | String} blockWidth 滑块宽度高等于宽30
* @property {Number | String} height 滑块条高度单位rpx默认6
* @property {String} inactiveColor 底部条背景颜色(默认#c0c4cc
* @property {String} activeColor 底部选择部分的背景颜色(默认#2979ff
* @property {String} blockColor 滑块颜色(默认#ffffff
* @property {Object} blockStyle 给滑块自定义样式,对象形式
* @property {Boolean} disabled 是否禁用滑块(默认为false)
* @event {Function} changing 正在滑动中
* @event {Function} change 滑动结束
* @example <up-slider v-model="value" />
*/
export default {
name: 'u-slider',
mixins: [mpMixin, mixin, props],
emits: ["start", "changing", "change", "update:modelValue"],
data() {
return {
startX: 0,
status: 'end',
newValue: 0,
distanceX: 0,
startValue0: 0,
startValue: 0,
barStyle0: {},
barStyle: {},
sliderRect: {
left: 0,
width: 0
}
};
},
watch: {
// #ifdef VUE3
modelValue(n) {
// 只有在非滑动状态时才可以通过value更新滑块值这里监听是为了让用户触发
if(this.status == 'end') this.updateValue(this.modelValue, false);
},
// #endif
// #ifdef VUE2
value(n) {
// 只有在非滑动状态时才可以通过value更新滑块值这里监听是为了让用户触发
if(this.status == 'end') this.updateValue(this.value, false);
}
// #endif
},
created() {
},
async mounted() {
// 获取滑块条的尺寸信息
if (!this.useNative) {
// #ifndef APP-NVUE
this.$uGetRect('.u-slider__base').then(rect => {
this.sliderRect = rect;
this.init()
});
// #endif
// #ifdef APP-NVUE
await sleep(30) // 不延迟会出现size获取都为0的问题
const ref = this.$refs['u-slider__base']
ref &&
dom.getComponentRect(ref, (res) => {
// console.log(res)
this.sliderRect = {
left: res.size.left,
width: res.size.width
};
this.init()
})
// #endif
}
},
methods: {
addStyle,
getPx,
init() {
if (this.isRange) {
this.updateValue(this.rangeValue[0], false, 0);
this.updateValue(this.rangeValue[1], false, 1);
} else {
// #ifdef VUE3
this.updateValue(this.modelValue, false);
// #endif
// #ifdef VUE2
this.updateValue(this.value, false);
// #endif
}
},
// native拖动过程中触发
changingHandler(e) {
const {
value
} = e.detail
// 更新v-model的值
// #ifdef VUE3
this.$emit("update:modelValue", value);
// #endif
// #ifdef VUE2
this.$emit("input", value);
// #endif
// 触发事件
this.$emit('changing', value)
},
// native滑动结束时触发
changeHandler(e) {
const {
value
} = e.detail
// 更新v-model的值
// #ifdef VUE3
this.$emit("update:modelValue", value);
// #endif
// #ifdef VUE2
this.$emit("input", value);
// #endif
// 触发事件
this.$emit('change', value);
},
onTouchStart(event, index = 1) {
if (this.disabled) return;
this.startX = 0;
// 触摸点集
let touches = event.touches[0];
// 触摸点到屏幕左边的距离
this.startX = touches.clientX;
// 此处的this.modelValue虽为props值但是通过$emit('update:modelValue')进行了修改
if (this.isRange) {
this.startValue0 = this.format(this.rangeValue[0], 0);
this.startValue = this.format(this.rangeValue[1], 1);
} else {
// #ifdef VUE3
this.startValue = this.format(this.modelValue);
// #endif
// #ifdef VUE2
this.startValue = this.format(this.value);
// #endif
}
// 标示当前的状态为开始触摸滑动
this.status = 'start';
let clientX = 0;
// #ifndef APP-NVUE
clientX = touches.clientX;
// #endif
// #ifdef APP-NVUE
clientX = touches.screenX;
// #endif
this.distanceX = clientX - this.sliderRect.left;
// 获得移动距离对整个滑块的值,此为带有多位小数的值,不能用此更新视图
// 否则造成通信阻塞需要每改变一个step值时修改一次视图
this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + parseFloat(this.min);
this.status = 'moving';
// 发出moving事件
this.$emit('changing');
this.updateValue(this.newValue, true, index);
},
onTouchMove(event, index = 1) {
if (this.disabled) return;
// 连续触摸的过程会一直触发本方法,但只有手指触发且移动了才被认为是拖动了,才发出事件
// 触摸后第一次移动已经将status设置为moving状态故触摸第二次移动不会触发本事件
if (this.status == 'start') this.$emit('start');
let touches = event.touches[0];
console.log('touchs', touches)
// 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
let clientX = 0;
// #ifndef APP-NVUE
clientX = touches.clientX;
// #endif
// #ifdef APP-NVUE
clientX = touches.screenX;
// #endif
this.distanceX = clientX - this.sliderRect.left;
// 获得移动距离对整个滑块的值,此为带有多位小数的值,不能用此更新视图
// 否则造成通信阻塞需要每改变一个step值时修改一次视图
this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + parseFloat(this.min);
this.status = 'moving';
// 发出moving事件
this.$emit('changing');
this.updateValue(this.newValue, true, index);
},
onTouchEnd(event, index = 1) {
if (this.disabled) return;
if (this.status === 'moving') {
this.updateValue(this.newValue, false, index);
this.$emit('change');
}
this.status = 'end';
},
onTouchStart2(event, index = 1) {
if (!this.isRange) {
// this.onChangeStart(event, index);
}
},
onTouchMove2(event, index = 1) {
if (!this.isRange) {
// this.onTouchMove(event, index);
}
},
onTouchEnd2(event, index = 1) {
if (!this.isRange) {
// this.onTouchEnd(event, index);
}
},
onClick(event) {
// if (this.isRange) return;
if (this.disabled) return;
// 直接点击滑块的情况计算方式与onTouchMove方法相同
// console.log('click', event)
// #ifndef APP-NVUE
// nvue下暂时无法获取坐标
let clientX = event.detail.x - this.sliderRect.left
this.newValue = ((clientX / this.sliderRect.width) * (this.max - this.min)) + parseFloat(this.min);
this.updateValue(this.newValue, false, 1);
// #endif
},
updateValue(value, drag, index = 1) {
// 去掉小数部分同时也是对step步进的处理
let valueFormat = this.format(value, index);
// 不允许滑动的值超过max最大值
if(valueFormat > this.max ) {
valueFormat = this.max
}
// 设置移动的距离不能用百分比因为NVUE不支持。
let width = Math.min((valueFormat - this.min) / (this.max - this.min) * this.sliderRect.width, this.sliderRect.width)
let barStyle = {
width: width + 'px'
};
// 移动期间无需过渡动画
if (drag == true) {
barStyle.transition = 'none';
} else {
// 非移动期间删掉对过渡为空的声明让css中的声明起效
delete barStyle.transition;
}
// 修改value值
if (this.isRange) {
this.rangeValue[index] = valueFormat;
this.$emit("update:modelValue", this.rangeValue);
} else {
// #ifdef VUE3
this.$emit("update:modelValue", valueFormat);
// #endif
// #ifdef VUE2
this.$emit("input", valueFormat);
// #endif
}
switch (index) {
case 0:
this.barStyle0 = {...barStyle};
break;
case 1:
this.barStyle = {...barStyle};
break;
default:
break;
}
},
format(value, index = 1) {
// 将小数变成整数,为了减少对视图的更新,造成视图层与逻辑层的阻塞
if (this.isRange) {
switch (index) {
case 0:
return Math.round(
Math.max(this.min, Math.min(value, this.rangeValue[1] - parseInt(this.step),this.max))
/ parseInt(this.step)
) * parseInt(this.step);
break;
case 1:
return Math.round(
Math.max(this.min, this.rangeValue[0] + parseInt(this.step), Math.min(value, this.max))
/ parseInt(this.step)
) * parseInt(this.step);
break;
default:
break;
}
} else {
return Math.round(
Math.max(this.min, Math.min(value, this.max))
/ parseInt(this.step)
) * parseInt(this.step);
}
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-slider {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
&__native {
flex: 1;
}
&-inner {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
border-radius: 999px;
padding: 10px 18px;
justify-content: center;
}
&__show-value {
margin: 10px 18px 10px 0px;
}
&__show-range-value {
padding-top: 2px;
font-size: 12px;
line-height: 12px;
position: absolute;
bottom: 0;
}
&__base {
background-color: #ebedf0;
}
/* #ifndef APP-NVUE */
&-inner:before {
position: absolute;
right: 0;
left: 0;
content: '';
top: -8px;
bottom: -8px;
z-index: -1;
}
/* #endif */
&__gap {
position: relative;
border-radius: 999px;
transition: width 0.2s;
background-color: #1989fa;
}
&__button {
width: 24px;
height: 24px;
border-radius: 50%;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
background-color: #fff;
transform: scale(0.9);
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
&__button-wrap {
position: absolute;
// transform: translate3d(50%, -50%, 0);
}
&--disabled {
opacity: 0.5;
}
}
</style>