123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- <template>
- <view class="l-slider"
- :class="[
- 'l-slider--' + (vertical ? 'vertical': 'horizontal'),{
- 'l-slider--disabled': disabled}
- ]"
- :style="[styles]"
- ref="sliderRef"
- @touchstart.stop="touchstart"
- @touchmove.stop="touchmove"
- @touchend="touchend">
- <view ref="railRef" class="l-slider__rail"
- :class="['l-slider__rail--' + (vertical ? 'vertical': 'horizontal')]"
- :style="[railStyle]">
- <view class="l-slider__track"
- :class="['l-slider__track--' + (vertical ? 'vertical': 'horizontal')]"
- :style="[progressBarStyle]" ref="progressBarRef"></view>
- </view>
- <view class="l-slider__thumb-wrapper"
- :class="['l-slider__thumb-wrapper--' + (vertical ? 'vertical': 'horizontal')]"
- :style="[startThumbStyle]"
- ref="startThumbRef"
- data-thumb="0">
- <slot name="start-thumb">
- <view class="l-slider__thumb" :style="[thumbStyle]"></view>
- </slot>
- </view>
-
- <view class="l-slider__thumb-wrapper"
- :class="['l-slider__thumb-wrapper--' + (vertical ? 'vertical': 'horizontal')]"
- :style="[endThumbStyle]"
- ref="endThumbRef"
- data-thumb="1"
- v-if="range">
- <slot name="end-thumb">
- <view class="l-slider__thumb" :style="[thumbStyle]"></view>
- </slot>
- </view>
- </view>
- </template>
- <script lang="ts">
- // @ts-nocheck
- import { computed, defineComponent, getCurrentInstance, nextTick, onMounted, onUnmounted ,ref, watch, reactive } from '@/uni_modules/lime-shared/vue';
- import sliderProps from './props'
- import { SliderValue } from './type';
- import { getRect } from '@/uni_modules/lime-shared/getRect';
- import { closest } from '@/uni_modules/lime-shared/closest';
- import { useTouch } from './touch'
- import { format } from './utils'
-
- export default defineComponent({
- name: 'l-slider',
- props: sliderProps,
- emits: ['change', 'update:modelValue', 'change-position'],
- setup(props, {emit}) {
- const instance = getCurrentInstance().proxy!
- const sliderRect = reactive({
- width: 0,
- height: 0,
- 'min-height': '',
- 'min-width': '',
- left: 0,
- top: 0
- })
- const railRect = reactive({
- width: 0,
- height: 0,
- left: 0,
- top: 0
- })
- const progressBarRect = reactive({
- width: null,
- height: null,
- left: null,
- opacity: 1,
- top: null
- })
- const thumbRect = reactive({
- width: 0,
- height: 0,
- left: 0,
- top: 0
- })
- const startThumbStyle = reactive({
- left: null,
- top: null
- })
- const endThumbStyle = reactive({
- left: null,
- top: null
- })
-
- const tempValue = ref<number[]>([0, 0]);
- const innerValue = computed({
- set(value : SliderValue) {
- emit('change', value);
- emit('update:modelValue', value);
- // #ifdef VUE2
- emit('input', value);
- // #endif
- },
- get() : SliderValue {
- return props.value ?? props.modelValue ?? props.min
- }
- })
- const isRange = computed(() : boolean => props.range && Array.isArray(innerValue.value) && innerValue.value.length == 2);
- const direction = computed(() : Map<string, string> => {
- const map = new Map<string, string>();
- map.set('left', props.vertical ? 'top' : 'left');
- map.set('right', props.vertical ? 'bottom' : 'right');
- map.set('width', props.vertical ? 'height' : 'width');
- map.set('min-height', props.vertical ? 'min-width' : 'min-height');
- map.set('vertical', props.vertical ? 'vertical' : 'horizontal');
- // map.set('margin-top', props.vertical ? 'margin-left' : 'margin-top');
- // map.set('margin-bottom', props.vertical ? 'margin-right' : 'margin-bottom');
- return map
- })
- const getDirection = (key : string) : string => {
- return direction.value.get(key) ?? key
- }
-
- const styles = computed(()=>{
- const style:Record<string, any> = {};
- const minHeight = getDirection('min-height')
- style[minHeight] = sliderRect[minHeight]
- return style
- })
- const railStyle = computed(() => {
- const style:Record<string, any> = {};
- if (props.railColor) {
- style['background'] = props.railColor!
- }
- if (props.railSize) {
- style[props.vertical ? 'width': 'height'] = props.railSize!
- }
- return style
- })
-
- const thumbStyle = computed(() => {
- const style:Record<string, any> = {};
- if (props.thumbColor) {
- style['background'] = props.thumbColor!
- }
- if(props.thumbBorderColor) {
- style['border-color'] = props.thumbBorderColor!
- }
- if (props.thumbSize) {
- style['width'] = props.thumbSize!
- style['height']= props.thumbSize!
- }
- return style
- })
-
- const progressBarStyle = computed(() : Map<string, any> => {
- const style:Record<string, any> = {};
- if (props.trackColor) {
- style['background'] = props.trackColor!
- }
- const left = getDirection('left')
- const width = getDirection('width')
- style[left] = progressBarRect[left]
- style[width] = progressBarRect[width]
- style['opacity'] = progressBarRect.opacity
-
- return style
- })
-
- const scope = computed(() : number => props.max - props.min);
-
- // 计算选中条的长度占轨道总长度的比例
- const calculateSelectedBarRatio = () : number => {
- const { min } = props;
- if (isRange.value) {
- // 对于范围选择,计算两个值之间的差值相对于总范围的比值
- return Math.abs(tempValue.value[1] - tempValue.value[0]) / scope.value;
- }
- // 对于单个值选择,计算该值相对于总范围的比值
- return (tempValue.value[0] - min) / scope.value;
- };
- // 计算选中条起始位置的偏移量占轨道总长度的比例
- const calculateStartOffsetRatio = () : number => {
- const { min } = props;
- if (isRange.value) {
- // 对于范围选择,计算起始值相对于总范围的偏移量比值
- const _startValue = Math.min(tempValue.value[0], tempValue.value[1]);
- return (_startValue - min) / scope.value;
-
- }
- // 对于单个值选择,偏移量为0
- return 0;
- };
- // 计算轨道的最大尺寸(宽或高)
- const calculateMaxTrackDimension = () : number[] => {
- const railDimension = props.vertical ? railRect.width : railRect.height;
- const thumbDimension = props.vertical ? thumbRect.width : thumbRect.height;
- return [Math.max(railDimension, thumbRect.width, thumbRect.height), Math.max(railDimension, thumbDimension)];
- };
- // 计算轨道的总长度
- const calculateTrackLength = () : number => {
- return props.vertical ? railRect.height : railRect.width
- };
- // 更新进度条和滑块的位置
- const updateProgressBarAndThumb = () => {
- let trackLength = calculateTrackLength();
- const selectedBarRatio = calculateSelectedBarRatio();
- const startOffsetRatio = calculateStartOffsetRatio();
- const [start, end] = tempValue.value;
- const [maxDimension, thumbDimension] = calculateMaxTrackDimension();
- const thumbHalf = maxDimension / 2;
- const usableTrackLength = trackLength - maxDimension;
-
- progressBarRect[getDirection('left')] = `${startOffsetRatio * usableTrackLength}px`
- progressBarRect[getDirection('width')] = `${(selectedBarRatio * usableTrackLength + maxDimension)}px`
- progressBarRect[getDirection('opacity')] = selectedBarRatio == 0 && start == props.min ? 0 : 1
- sliderRect[getDirection('min-height')] = `${thumbDimension}px`
-
- // 更新滑块位置
- const startThumbPosition = (start - props.min) / scope.value * usableTrackLength + thumbHalf;
- startThumbStyle[getDirection('left')] = `${startThumbPosition}px`
- if (!isRange.value) return;
- const endThumbPosition = (end - props.min) / scope.value * usableTrackLength + thumbHalf;
- endThumbStyle[getDirection('left')] = `${endThumbPosition}px`
- }
-
- const touch = useTouch()
- const getDelta = (startX : number, startY : number) : number => {
- // #ifdef WEB
- const topHeight = document.querySelector('.uni-page-head').offsetHeight
- // #endif
- // #ifndef WEB
- const topHeight = 0
- // #endif
- return props.vertical ? startY - sliderRect.top - topHeight : startX - sliderRect.left;
- }
- let startValue = 0
- const currentKey = ref(0)
- const dragStatus = ref<DragStatus>('')
- const touchstart = (event : UniTouchEvent) => {
- if (props.disabled || dragStatus.value != '') return;
- // event.preventDefault()
- dragStatus.value = 'start'
- touch.start(event);
-
- // #ifdef VUE3
- const { startX, startY } = touch;
- const delta = getDelta(startX.value, startY.value)
- // #endif
- // #ifdef VUE2
- const delta = getDelta(event.touches[0].pageX, event.touches[0].pageY)
- // #endif
- const total = calculateTrackLength()
- let value = format(delta / total * scope.value + props.min, props.min, props.max, props.step)
- if (isRange.value) {
- currentKey.value = tempValue.value.findIndex((it) : boolean => it == closest(tempValue.value, value))
- tempValue.value[currentKey.value] = value
- innerValue.value = [...tempValue.value]
- } else {
- currentKey.value = 0;
- innerValue.value = value
- }
- startValue = value;
- }
- const touchmove = (event : UniTouchEvent) => {
- if (props.disabled) return;
- // event.preventDefault()
- dragStatus.value = 'dragging'
- touch.move(event);
- const key = currentKey.value
- const delta = props.vertical ? touch.deltaY : touch.deltaX;
- const total = calculateTrackLength()
- const diff = delta.value / total * scope.value;
- const value = format(startValue + diff, props.min, props.max, props.step)
- tempValue.value[key] = value
- if (isRange.value) {
- innerValue.value = [...tempValue.value]
- } else {
- innerValue.value = value
- }
- }
- const touchend = (_event : UniTouchEvent) => {
- if (props.disabled) return;
- dragStatus.value = ''
- if (!isRange.value) {
- currentKey.value = 0
- } else {
- currentKey.value = -1
- }
- }
-
-
- const setValue = (v: SliderValue) => {
- // if(dragStatus.value == '') {
- if (isRange.value) {
- tempValue.value = v.map((value) : number => format(value, props.min, props.max, props.step))
- } else {
- tempValue.value[0] = format(innerValue.value as number, props.min, props.max, props.step)
- }
- // }
- nextTick(updateProgressBarAndThumb)
- }
-
- const stopWatch = watch(innerValue, setValue, { deep: true })
-
- const init = () => {
- Promise.all([
- getRect('.l-slider', instance),
- getRect('.l-slider__rail', instance),
- getRect('.l-slider__thumb-wrapper', instance),
- ]).then(([slider, rail, thumb])=>{
- sliderRect.width = slider.width;
- sliderRect.height = slider.height;
- sliderRect.left = slider.left;
- sliderRect.top = slider.top;
-
- railRect.width = rail.width;
- railRect.height = rail.height;
- railRect.left = rail.left;
- railRect.top = rail.top;
-
- thumbRect.width = thumb.width;
- thumbRect.height = thumb.height;
- thumbRect.left = thumb.left;
- thumbRect.top = thumb.top;
-
- setValue(innerValue.value)
- })
-
- }
-
-
- onMounted(()=>{
- nextTick(init)
- })
- onUnmounted(stopWatch)
-
- return {
- styles,
- railStyle,
- thumbStyle,
- progressBarStyle,
- startThumbStyle,
- endThumbStyle,
- touchstart,
- touchmove,
- touchend
- }
-
- }
- })
- </script>
- <style lang="scss">
- @import './index-u';
- </style>
|