基于Uniapp + VUE3.0 + TS 实现一个支持双滑块的Slider滑块组件

先看图:
在这里插入图片描述

<template>
    <view class="v-slider">
        <view class="v-slider-wrapper">
            <view class="v-slider-tap-area">
                <view 
                    class="v-slider-handle-wrapper"
                    :style="`background-color: ${props.backgroundColor};`">
                    <view class="v-slider-step"></view>
                    <view 
                        class="v-slider-track"
                        :style="trackStyle">
                    </view>
                    <view 
                        class="v-slider-thumb thumb-left"
                        :style="`
                            background-color: ${props.blockColor};
                            width: ${thumbSize}rpx;
                            height: ${thumbSize}rpx;
                            margin-left: -${props.blockSize}rpx;
                            margin-top: -${props.blockSize}rpx;
                            left: ${thumbLeft};
                        `"
                        @touchmove="e => handleTouchMove(e, 0)"
                        @touchend="handleTouchEnd">
                    </view>
                    <view 
                        class="v-slider-thumb thumb-right"
                        :style="`
                            background-color: ${props.blockColor};
                            width: ${thumbSize}rpx;
                            height: ${thumbSize}rpx;
                            margin-right: -${props.blockSize}rpx;
                            margin-top: -${props.blockSize}rpx;
                            right: ${thumbRight};
                        `"
                        @touchmove="e => handleTouchMove(e, 1)"
                        @touchend="handleTouchEnd">
                    </view>
                </view>
            </view>
        </view>
        <view 
            v-if="props.showRange"
            class="v-slider-range">
            <text 
                v-for="item of rangeList" 
                :key="item" 
                class="v-slider-range-item"
                :style="`left: ${item.left}%;`">
                <text class="text">{{ item.value }}</text>
            </text>
        </view>
    </view>
</template>
<script lang="ts" setup>
    import { ref, shallowRef, withDefaults, defineProps, defineEmits, watch, computed, onMounted } from 'vue'
	type TypeOjbect = Record<string, any>
    type ModelValue= [number, number]
    interface UniProps { 
        readonly min?: number,              // 最小值, 默认 0
        readonly max?: number,              // 最大值, 默认 100
        readonly step?: number,             // 步长,取值必须大于 0,并且可被(max - min)整除
        readonly modelValue?: ModelValue,   // 当前取值, 默认[0, 100]
        readonly backgroundColor?: string,  // 背景条颜色, 默认 #e9e9e9
        readonly activeColor?: string,      // 已选择的颜色, 默认 #1aad19
        readonly blockSize?: number,        // 滑块的大小, 默认为28, 取值范围为 12 - 28
        readonly blockColor?: string,       // 滑块的颜色, 默认 #ffffff
        readonly showRange?: boolean,       // 是否显示范围列表
        readonly rangeStep?: number         // 范围列表步阶
    }
    interface Emits {
        (event: 'update:modelValue', val: ModelValue): void, // eslint-disable-line
        // 完成一次拖动后触发的事件,event.detail = {value}
        (event: 'change', val: ModelValue): void // eslint-disable-line
        // 拖动过程中触发的事件,event.detail = {value}
        (event: 'changing', val: ModelValue): void // eslint-disable-line
    }

    // 属性初始值
    const props = withDefaults(defineProps<UniProps>(), {
        step: 1,
        backgroundColor: '#e9e9e9',
        activeColor: '#1aad19',
        blockSize: 28,
        blockColor: '#ffffff',
        showRange: true,
        rangeStep: 5
    })
    const emits = defineEmits<Emits>()
    const rangeValue = ref<ModelValue>([0, 100])
    const [min, max] = [shallowRef<number>(0), shallowRef<number>(100)]
    const { pixelRatio } = getApp().globalData
    const checkVal = function(val: number):number {
        if (val > max.value) {
            val = max.value
        } else if (val < min.value) {
            val = min.value
        }
        return val
    }

    // 监听范围最大、最小值
    watch<[number, number], true>(
        () => [props.min, props.max], 
        ([minVal, maxVal]) => {
            if (minVal) min.value = minVal
            if (maxVal) max.value = maxVal
        },
    	{
    	    immediate: true
    	}
    )
    
    // 监听范围值
	watch<ModelValue, true>(
    	() => props.modelValue,
    	val => {
            if (val) {
                let newVal = [...val]
                newVal = newVal.map(v => checkVal(v))
			    rangeValue.value = newVal
            }
    	},
    	{
    	    immediate: true
    	}
	)

    // 获取背景条长度
    let handleLine: TypeObject
    const getLineWidth = function():void {
        const query = uni.createSelectorQuery()
        query.select(".v-slider-handle-wrapper").boundingClientRect((e: DOMRect) => {
            const { left, width, height, right } = e
            handleLine = { left, width, height, right }
	    })
        query.exec()
    }

    // 范围差
    const rangeDiff = computed<number>(() => max.value - min.value)

    // 范围列表
    const rangeList = computed<TypeObject[]>(() => {
        const [minVal, maxVal] = [min.value, max.value]
        const step = (maxVal - minVal) / props.rangeStep
        const range = []
        for (let i = 0; i <= props.rangeStep; i++) {
            const value = Math.ceil(minVal + i * step)
            const left = (value - minVal) * 100 / rangeDiff.value
            range.push({ value, left })
        }
        return range
    })

    // 尺寸
    const thumbSize = computed<number>(() => props.blockSize * pixelRatio)

    // 获取百分比
    const getPercent = function(diff: number): string {
        if (diff < 0) diff = 0
        return Math.floor(diff * 100 / rangeDiff.value) + '%'
    }

    // 滑块thumb左侧浮动值
    const thumbLeft = computed<string>(() => {
        return getPercent(rangeValue.value[0] - min.value)
    })
    // 滑块thumb右侧浮动值
    const thumbRight = computed<string>(() => {
        return getPercent(max.value - rangeValue.value[1])
    })

    // 滑块轨道track css 样式
    const trackStyle = computed<string>(() => {
        const start = rangeValue.value[0]
        const end = rangeValue.value[1]
        const options = {
            "background-color": props.activeColor,
            "width": getPercent(Math.abs(rangeValue.value[1] - rangeValue.value[0]))
        } as TypeObject
        if (start <= end) {
            options.left = getPercent(start - min.value)
        } else {
            options.left = getPercent(end - min.value)
        }
        let style = ''
        Object.entries(options).forEach(option => {
            style += `${option[0]}: ${option[1]};`
        })
        return style
    })

    // 当前取值
    let timer:ReturnType<typeof setTimeout>
    const handleTouchMove = function(e: TouchEvent, index: number):void {
        if (timer) clearTimeout(timer)
        // 防抖
        timer = setTimeout(function() {
            const { pageX } = e.touches[0]
            const { left, width } = handleLine
            let value = (pageX - left) * rangeDiff.value / width  + min.value
            if (value % props.step > 0) {
                value = Math.round(value / props.step) * props.step
            }
            if (value < min.value) {
                value = min.value
            } else if (value > max.value) {
                value = max.value
            }
            rangeValue.value[index] = Math.floor(value * 100) / 100
            emits('update:modelValue', rangeValue.value)
            emits('changing', rangeValue.value)
        }, 10)
    }

    // 滑动停止事件
    const handleTouchEnd = function():void {
        emits('change', rangeValue.value)
    }
    onMounted(() => {
        getLineWidth()
    })
</script>
<style lang="scss">
    .v-slider {
        margin: 16rpx 28rpx;
        padding: 0;
        display: block;
        width: 100%;
        .v-slider-wrapper {
            display: -webkit-flex;
            display: flex;
            -webkit-align-items: center;
            align-items: center;
            min-height: 16px;
        }
        .v-slider-tap-area {
            -webkit-flex: 1;
            flex: 1;
            padding: 8px 0;
        }
        .v-slider-handle-wrapper {
            position: relative;
            z-index: 0;
            height: 2px;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s ease;
            -webkit-tap-highlight-color: transparent;
        }
        .v-slider-thumb {
            z-index: 2;
            box-shadow: 0 0 4px rgb(0 0 0 / 20%);
            position: absolute;
            top: 50%;
            cursor: pointer;
            border-radius: 50%;
            transition: border-color 0.3s ease;
        }
        .v-slider-step {
            position: absolute;
            width: 100%;
            height: 2px;
            background: transparent;
            z-index: 1;
        }
        .v-slider-track {
            height: 100%;
            border-radius: 6px;
            transition: background-color 0.3s ease;
            position: absolute;
            z-index: 1;
        }
        .v-slider-range {
            width: 100%;
            margin: 24rpx 0 0;
            padding-bottom: 20rpx;
            position: relative;
            .v-slider-range-item {
                text-align: center;
                font-size: 28rpx;
                position: absolute;
                z-index: 1;
                .text {
                    width: 80rpx;
                    position: absolute;
                    z-index: 2;
                    left: -40rpx;
                }
            }
        }
    }
</style>
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Uniapp Vue3 TS中创建项目的详细过程如下:1.首先,安装Vue CLI,这是一个用于构建Vue应用程序的命令行界面工具;2.安装uniapp,使用npm或者yarn安装uniapp;3.使用Vue CLI创建一个新的Vue应用;4.在Vue应用中安装uniapp;5.使用uniapp CLI安装需要的组件;6.使用uniapp CLI构建项目;7.使用uniapp CLI运行项目;8.部署应用程序。 ### 回答2: 在UniApp中使用Vue3和TypeScript创建一个项目的详细过程如下: 1. 环境准备:确保已安装Node.js和npm包管理器,然后使用命令行工具执行以下命令安装UniApp CLI工具:`npm install -g @vue/cli @vue/cli-init` 2. 创建项目:在命令行中执行以下命令创建一个UniApp项目:`vue create -p dcloudio/uni-preset-vue@next your-project-name`。 3. 过程中会提示选择预设(Presets)类型,选择"Manually select features"手动选择特性。 4. 选择特性:通过方向键选择"TypeScript"并按空格进行选择,然后按Enter键确认。 5. 选择样式预处理器(Stylus/Sass/Less):根据需要选择相应的样式预处理器,或选择"None"不使用。 6. 选择静态资源目录:根据需要选择存放静态资源的目录,或选择默认值。 7. 选择ESLint代码检查工具和配置:根据需要选择是否使用ESLint检查代码规范,以及设置相应的规则。 8. 选择保存配置或预设:根据个人需要选择是否保存配置或使用预设。 9. 安装依赖:根据命令行提示,在项目根目录下执行`npm install`命令安装项目所需的依赖。 10. 启动开发服务器:依然在命令行中执行`npm run dev:%PLATFORM%`(%PLATFORM%为平台标识,如h5、app-plus等),启动开发服务器,进行项目开发。 11. 创建页面:在项目的`src/pages/`目录下创建相应的页面,例如`src/pages/index/`,并在该目录下创建`index.vue`作为页面的入口文件。 12. 编写代码:使用Vue3和TypeScript编写页面逻辑和样式代码。 13. 预览页面:在开发服务器启动后,打开浏览器,访问相应的开发链接(如http://localhost:%PORT%)即可预览页面效果。 14. 进行开发和测试:根据需求,在页面中添加事件处理、数据请求等逻辑,并进行开发和测试。 15. 打包发布:完成页面开发和测试后,执行相应的命令进行打包发布。例如使用`npm run build:%PLATFORM%`(%PLATFORM%为平台标识,如h5、app-plus等)命令打包相应平台的代码。 以上就是在UniApp中使用Vue3和TypeScript创建项目的详细过程。根据个人需求和项目的特点,还可以进一步配置和调整相应的开发环境、插件和工具等。 ### 回答3: 在Uniapp中使用Vue3和TypeScript创建一个项目的详细过程如下: 1. 首先,确保已经安装了Node.js和npm(Node.js自带)。 2. 打开终端命令行窗口,进入要创建项目的文件夹。 3. 运行以下命令安装uniapp-cli: ``` npm install -g @vue/cli @vue/cli-init ``` 4. 使用以下命令创建一个新的uniapp项目: ``` vue create -p dcloudio/uni-preset-vue my-project ``` 5. 创建过程中会提示选择预设配置,选择"Manually select features"并按回车。 6. 在选择特性的界面上,选择"TypeScript"并按回车。 7. 继续选择其他你需要的特性,或者直接按回车跳过。 8. 等待项目依赖安装完成。 9. 进入项目文件夹: ``` cd my-project ``` 10. 运行以下命令启动开发服务器: ``` npm run dev:mp-weixin ``` 11. 打开微信开发者工具,导入项目。 在微信开发者工具中,选择"导入项目",然后选择项目文件夹。 12. 选择小程序开发目录为项目文件夹中的`dist/dev/mp-weixin`文件夹。 13. 点击确定,等待项目导入完成。 14. 点击微信开发者工具的"编译"按钮,以确保项目成功运行。 通过以上步骤,你已经成功创建了一个基于UniappVue3和TypeScript项目,并且可以在微信开发者工具中进行调试和开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值