基于Taro + React 实现微信小程序半圆滑块组件、半圆进度条、弧形进度条、半圆滑行轨道(附源码)

效果:

功能点:

1、四个档位

2、可点击加减切换档位

3、可以点击区域切换档位

4、可以滑动切换档位

目的:

给大家提供一些实现思路,找了一圈,一些文章基本不能直接用,错漏百出,代码还藏着掖着,希望可以帮到大家

代码

ts的写法风格

index.tsx     

import { View, ITouchEvent, BaseTouchEvent } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState } from 'react'
import styles from './index.module.less'
import classNames from 'classnames'
import { debounce } from '~/utils/util'enum ANGLES {ANGLES_135 = -135,ANGLES_90 = -90,ANGLES_45 = -45,ANGLES_0 = 0
}enum MODE_VALUE {MODE_1 = 1,MODE_2 = 2,MODE_3 = 3,MODE_4 = 4
}const HalfCircle = () => {const [state, setState] = useState({originAngle: ANGLES.ANGLES_135,isTouch: false,val: MODE_VALUE.MODE_1,originX: 0,originY: 0})/** 半圆的半径 */const RADIUS = 150/** 半径的一半 */const RADIUS_HALF = RADIUS / 2/** 4/3 圆的直径 */const RADIUS_THIRD = RADIUS_HALF * 3/** 直径 */const RADIUS_DOUBLE = RADIUS * 2/** 误差 */const DEVIATION = 25/** 是否开启点击振动 */const isVibrateShort = trueconst getAngle = () => {return {transform: `rotate(${state.originAngle}deg)`,transition: `all ${state.isTouch ? ' 0.2s' : ' 0.55s'}`}}/*** 根据坐标判断是否在半圆轨道上,半圆为RADIUS,误差为DEVIATION* @param pageX* @param pageY*/const isInHalfCircleLine = (pageX: number, pageY: number, deviation?: number) => {const DEVIATION_VALUE = deviation || DEVIATIONconst squareSum = (pageX - RADIUS) * (pageX - RADIUS) + (pageY - RADIUS) * (pageY - RADIUS)const min = (RADIUS - DEVIATION_VALUE) * (RADIUS - DEVIATION_VALUE)const max = (RADIUS + DEVIATION_VALUE) * (RADIUS + DEVIATION_VALUE)return squareSum >= min && squareSum <= max}/** 根据做标点,获取档位 0 -> 4, -45 -> 3, -90 -> 2, -135 -> 1,从而获取旋转的角度 */const setGear = (pageX: number, pageY: number) => {let val = state.vallet originAngle = state.originAngleif (isInHalfCircleLine(pageX, pageY)) {if (pageX > 0 && pageX <= RADIUS_HALF) {val = MODE_VALUE.MODE_1originAngle = ANGLES.ANGLES_135} else if (pageX > RADIUS_HALF && pageX <= RADIUS) {val = MODE_VALUE.MODE_2originAngle = ANGLES.ANGLES_90} else if (pageX > RADIUS && pageX <= RADIUS_THIRD) {val = MODE_VALUE.MODE_3originAngle = ANGLES.ANGLES_45} else {val = MODE_VALUE.MODE_4originAngle = ANGLES.ANGLES_0}}if (state.val === val) returnsetState((old) => {return {...old,originAngle,val}})if (isVibrateShort) {setTimeout(() => {Taro.vibrateShort()}, 200)}}/*** 滑动比较细腻,根据x轴坐标,calcX判断是否前进还是后退* @param pageX* @param pageY*/const setGearSibler = (pageX: number, pageY: number) => {let val = state.vallet originAngle = state.originAngleconst calcX = pageX - state.originX/** 把误差值增加,方便滑动 */if (isInHalfCircleLine(pageX, pageY, 50)) {if (pageX > 0 && pageX <= RADIUS_HALF) {if (calcX > 0) {/** 向前滑动,就前进一个档位 */val = MODE_VALUE.MODE_2originAngle = ANGLES.ANGLES_90} else {/** 向后滑动,就后退一个档位 */val = MODE_VALUE.MODE_1originAngle = ANGLES.ANGLES_135}} else if (pageX > RADIUS_HALF && pageX <= RADIUS) {if (calcX > 0) {val = MODE_VALUE.MODE_2originAngle = ANGLES.ANGLES_90} else {val = MODE_VALUE.MODE_1originAngle = ANGLES.ANGLES_135}} else if (pageX > RADIUS && pageX <= RADIUS_THIRD) {if (calcX > 0) {val = MODE_VALUE.MODE_3originAngle = ANGLES.ANGLES_45} else {val = MODE_VALUE.MODE_2originAngle = ANGLES.ANGLES_90}} else {if (calcX > 0) {val = MODE_VALUE.MODE_4originAngle = ANGLES.ANGLES_0} else {val = MODE_VALUE.MODE_3originAngle = ANGLES.ANGLES_45}}}setState((old) => {return {...old,originAngle,val}})}/*** 获取正确的坐标点* @param pageX* @param pageY* @returns*/const getRealXY = (pageX: number,pageY: number): Promise<{realX: numberrealY: number}> => {return new Promise((resolve) => {Taro.createSelectorQuery().select('#sliderBgcId').boundingClientRect((rect) => {const { left, top } = rect/** 获取真实的做标点 */const realX = pageX - leftconst realY = pageY - topresolve({realX,realY})}).exec()})}const onTouchEnd = (event: BaseTouchEvent<any>) => {setState((old) => {return {...old,isTouch: false}})}const onTouchMove = debounce(async (event: BaseTouchEvent<any>) => {const { pageX, pageY } = event.changedTouches[0]const { realX, realY } = await getRealXY(pageX, pageY)if (isInHalfCircleLine(realX, realY)) {setGearSibler(realX, realY)}}, 100)const onTouchStart = async (event: BaseTouchEvent<any>) => {const { pageX, pageY } = event.changedTouches[0]const { realX, realY } = await getRealXY(pageX, pageY)setState((old) => {return {...old,originX: realX,originY: realY,isTouch: true}})}/** 点击设置档位 */const onHandleFirstTouch = async (event: BaseTouchEvent<any>) => {const { pageX, pageY } = event.changedTouches[0]const { realX, realY } = await getRealXY(pageX, pageY)if (isInHalfCircleLine(realX, realY)) {setGear(realX, realY)}}const lose = () => {if (state.isTouch) returnif (state.val === 1) return Taro.showToast({title: '最低只能1挡',icon: 'error',duration: 2000})setState((old) => {return {...old,originAngle: state.originAngle - 45,val: state.val - 1}})if (isVibrateShort) {Taro.vibrateShort()}}const add = () => {if (state.isTouch) returnif (state.val === 4) return Taro.showToast({title: '最高只能4挡',icon: 'error',duration: 2000})setState((old) => {return {...old,originAngle: state.originAngle + 45,val: state.val + 1}})if (isVibrateShort) {Taro.vibrateShort()}}return (<ViewclassName={styles.slider}// onTouchEnd={(event) => onTouchEnd(event)}// onTouchMove={(event) => onTouchMove(event)}// onTouchStart={(event) => onTouchStart(event)}onClick={onHandleFirstTouch}><View className={styles.activeSliderSet}><View className={styles.activeSlider} style={getAngle()} /></View><View className={styles.origin} id="origin"><View className={styles.long} style={getAngle()}><ViewclassName={styles.circle}onTouchMove={(event) => onTouchMove(event as BaseTouchEvent<any>)}onTouchStart={(event) => onTouchStart(event as BaseTouchEvent<any>)}onTouchEnd={(event) => onTouchEnd(event as BaseTouchEvent<any>)}/></View></View>{/* 背景 */}<View className={styles.sliderBgc} id="sliderBgcId" />{/* 刻度 */}{/* <View className={styles.scaleBgc} /> */}<View className={styles.centerContent}><View className={styles.centerText}>能量档位</View><View className={styles.btn_air_bar}><View className={classNames(styles.btn_air, styles.btn_air_left)} onClick={lose}>-</View><View className={styles.val}><View className="val_text">{state.val}</View></View><View className={classNames(styles.btn_air, styles.btn_air_right)} onClick={add}>+</View></View></View></View>)
}export default HalfCircle

index.module.less

@color-brand: #EBC795 ;
@borderColor:#706D6D;
@sliderWidth:10px;
@radius:150px;
@long: 150px;
@border-radius: @long;.slider {position: relative;padding-bottom: @sliderWidth / 2;background-color: #000;width: 100vw;display: flex;justify-content: center;align-items: center;// 背景色.sliderBgc {width: @long*2;height: @long;border: @sliderWidth solid;border-radius: @border-radius  @border-radius 0 0;border-color: @borderColor;border-bottom: none;}.scaleBgc {width: @long*2 + @sliderWidth *2;height: @long + @sliderWidth;position: absolute;// bottom: 0;// left: 0;border: @sliderWidth solid;border-radius: @border-radius + @sliderWidth  @border-radius + @sliderWidth 0 0;border-color: transparent;border-bottom: none;top: -10px;background-clip: padding-box, border-box;background-origin: padding-box, border-box;background-image: linear-gradient(to right, #000, #000), linear-gradient(90deg, #FFD1B2, #E49E6B);}// 激活色.activeSliderSet {position: absolute;width: (@long) *2;height: @long;// left: 0;// bottom: 0;z-index: 2;overflow: hidden;.activeSlider {bottom: 0;left: 0;width: @long*2;height: @long;border: @sliderWidth solid;border-color: @color-brand;// border-color: transparent !important;border-radius: @border-radius  @border-radius 0 0;border-bottom: none;transform: rotate(-100deg);transform-origin: @long @long;// background-clip: padding-box, border-box;// background-origin: padding-box, border-box;// background-image: linear-gradient(to right, #000, #000), linear-gradient(90deg, #FFD1B2, #E49E6B);}}.origin {width: 0;height: 0;position: absolute;background-color: rgba(0, 0, 0, 0.1);bottom: 0;left: 50%;z-index: 11;transform: translateX(50%);.long {width: @long - (@sliderWidth / 2);height: 0;z-index: 9999;position: absolute;top: 0;left: 0;transform-origin: 0 0;.circle {width: 16px;height: 16px;border-radius: 50%;position: absolute;top: 50%;right: 0;transform: translate(50%, -50%);background-color: #000;border: #fff 4px solid;z-index: 999;padding: 5px;}}}}.centerContent {position: absolute;bottom: 0;left: 50%;transform: translateX(-50%);z-index: 99;margin-bottom: 20px;.centerText {text-align: center;color: var(--q-light-color-text-secondary, var(--text-secondary, #8C8C8C));font-size: 10px;margin-bottom: 25px;}.btn_air_bar {display: flex;align-items: center;.btn_air {width: 30px;height: 30px;border-radius: 50%;background-color: wheat;font-size: 16px;font-weight: 500;display: flex;align-items: center;justify-content: center;}.btn_air_left {background-color: #706D6D;color: white;}.btn_air_right {background-color: white;color: #706D6D;}.val {height: 26px;display: flex;align-items: center;margin: 0 30px;font-size: 26px;font-weight: 700;}}
} 

防抖的工具函数debounce 的详细代码:

import { debounce } from '~/utils/util'

function debounce<T extends Function>(func: T, delay: number): T {let timeoutreturn function (this: any, ...args: any[]): void {const context = this;clearTimeout(timeout);timeout = setTimeout(() => {func.apply(context, args);}, delay);} as any;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/129223.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 分组后统计 TopN 优化思路

一、表信息 表结构如下&#xff1a; CREATE TABLE score (id int(11) NOT NULL AUTO_INCREMENT,name varchar(255) DEFAULT NULL,score int(11) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1746687 DEFAULT CHARSETutf8;使用存储过程生成十万条测试数据&am…

JavaScript:事件循环机制(EventLoop)

一、理解进程、线程 进程是操作系统中的基本概念之一&#xff0c;指的是一个正在运行中的程序&#xff0c;包括了程序的执行代码、数据、资源等。操作系统为每个进程分配一定的系统资源&#xff0c;例如内存空间、文件和设备等&#xff0c;以便进程能够正常运行。 线程是进程…

Nginx简介,Nginx搭载负载均衡以及Nginx部署前端项目

目录 一. Nginx简介 Nginx的优点 二. Nginx搭载负载均衡 2.1 Nginx安装 2.1.1 安装依赖 2.1.2 解压nginx安装包 2.1.3 安装nginx 2.1.4 启动nginx服务 2.2 tomcat负载均衡 2.3 Nginx配置 三. Nginx前端部署 一. Nginx简介 NGINX&#xff08;读作&#xff1a;engi…

气膜场馆的降噪方法

在现代社会&#xff0c;噪音已经成为我们生活中难以避免的问题&#xff0c;而气膜场馆也不例外。传统的气膜场馆常常因其特殊结构而面临噪音扩散和回声问题&#xff0c;影响了人们的体验和活动效果。然而&#xff0c;随着科技的进步&#xff0c;多功能声学综合馆应运而生&#…

vscode开启emmet语法

需要在setting.json中添加配置 首先进入设置&#xff0c;然后点击右上角 Vue项目添加如下配置 "emmet.syntaxProfiles": { "vue-html": "html", "vue": "html" },React项目添加如下配置 "emmet.includeLanguages&quo…

大数据Doris(十五):Doris表的字段类型

文章目录 Doris表的字段类型 一、TINYINT数据类型 二、SMALLINT数据类型 三、INT数据类型

【Web】TCP 和 UCP 的含义和区别

文章目录 一、两者含义二、两者区别 一、两者含义 TCP/IP 协议组为传输层指明了两个协议&#xff1a;TCP 和 UDP&#xff0c;他们都是作为应用程序和网络操作的中介物 TCP &#xff08;传输控制协议&#xff09;&#xff1a;通过三次握手建立可靠的连接&#xff0c;发送端将数据…

Vue3 如何在<script setup>里设置组件name属性

Vue3 如何在<script setup>里设置组件name属性 文章目录 Vue3 如何在\<script setup>里设置组件name属性一、Vue组件中 name 的用处二、难看但实用的方法三、使用第三方插件支持安装插件插件基本配置插件基本使用 四、Vue官方解决方法4.1 Vue3.3版本之前安装插件插…

物理内存的组织形式

由于物理地址是连续的&#xff0c;页也是连续的&#xff0c;每个页大小也是一样的。因而对于任何一个地址&#xff0c;只要直接除一下每页的大小&#xff0c;很容易直接算出在哪一页。每个页有一个结构 struct page 表示&#xff0c;这个结构也是放在一个数组里面&#xff0c;这…

C#中只能在.NetFramework下使用LINQtoSQL不要在.net 下使用

目录 一、在net7.0下无法实现LINQtoSQL 1.VS上建立数据库连接 2.VS上创建LINQtoSQL 二、在.NetFramework4.8下成功实现LINQtoSQL 1.VS上建立数据库连接 2.VS上创建LINQtoSQL 三、结论 四、理由 本文是个人观点&#xff0c;因为我百般努力在.net7.0下无法实现LINQtoSQL的…

RAR Extractor v11.20(mac解压缩软件)

RAR Extractor是一款专门用于解压RAR格式压缩文件的软件&#xff0c;以下是关于RAR Extractor的详细介绍&#xff1a; 强大的解压功能&#xff1a;RAR Extractor能够解压RAR格式的压缩文件&#xff0c;无论是单一的RAR文件还是RAR文件包&#xff0c;都可以通过RAR Extractor进…

uniapp小程序刮刮乐抽奖

使用canvas画布画出刮刮乐要被刮的图片&#xff0c;使用移动清除画布。 当前代码封装为刮刮乐的组件&#xff1b; vue代码&#xff1a; <template><view class"page" v-if"merchantInfo.cdn_static"><image class"bg" :src&q…

多模块项目的搭建以及Nacos服务的发现与调用

这里写目录标题 多模块项目的搭建父项目的构建子模块的创建父子模块的意义 将注册服务引入到父子模块中创建子模块用于发现服务和调用供调用的服务接口创建调用子模块 测试一些小问题 在前文中我们实现了微服务的注册参考此文&#xff1a; Spring Cloud Alibaba中Nacos的安装&a…

基于RK3568的新能源储能能量管理系统ems

新能源储能能量管理系统&#xff08;EMS&#xff09;是一种基于现代化技术的系统&#xff0c;旨在管理并优化新能源储能设备的能量使用。 该系统通过监测、调度和控制新能源储能设备来确保能源的高效利用和可持续发展。 本文将从不同的角度介绍新能源储能能量管理系统的原理、…

what?腾讯云3年轻量2核4G5M服务器566.6元哪去了?

what&#xff1f;腾讯云3年轻量2核4G5M服务器566.6元哪去了&#xff1f;腾讯云双11优惠活动3年轻量2核4G5M服务器从566.6元涨价到756元三年&#xff0c;3年轻量2核2G4M服务器从366.6元恢复到540元三年&#xff0c;大家抓紧吧&#xff0c;三年轻量已经库存已经不多了&#xff0c…

RHCSA --- 第二天

一、查看IP地址 [rootlocalhost ~] ip ad 对应四张网卡 第一张&#xff1a;环回网卡&#xff08;用于测试&#xff09; 第二张&#xff08;主要&#xff09;&#xff1a;以太网网卡&#xff08;ens160&#xff09; 2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP>…

若依分离版——配置多数据源(mysql和oracle),实现一个方法操作多个数据源

目录 一、若依平台配置 二、编写oracle数据库访问的各类文件 三. 一个方法操作多个数据源 一、若依平台配置 1、在ruoyi-admin的pom.xml添加依赖 <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version…

ElasticSearch 批量插入漏数据

项目场景&#xff1a; 项目中需要把Mysql数据同步到ElasticSearch中 问题描述 数据传输过程中数据不时出现丢失的情况&#xff0c;偶尔会丢失一部分数据&#xff0c;本地测试也无法复现&#xff0c;后台程序也没有报错&#xff0c;一到正式环境就有问题,很崩溃 这里是批量操…

mysql基于软件包升级

注意&#xff1a;无论是什么升级都是有风险的&#xff0c;升级前都需要做一次全备份。 mysql简单备份和恢复-CSDN博客 本文章以5.7升级为8.0为案例演示。 0、准备 1、安装mysql5.7&#xff0c;5.7版本mysql安装演示mysql-linux归档版安装-CSDN博客 2、在官网下载8.0压缩包M…