当前位置: 首页 > news >正文

基于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;
}

相关文章:

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

效果&#xff1a; 功能点&#xff1a; 1、四个档位 2、可点击加减切换档位 3、可以点击区域切换档位 4、可以滑动切换档位 目的&#xff1a; 给大家提供一些实现思路&#xff0c;找了一圈&#xff0c;一些文章基本不能直接用&#xff0c;错漏百出&#xff0c;代码还藏着掖…...

城市内涝解决方案:实时监测,提前预警,让城市更安全

城市内涝积水问题是指城市地区在短时间内遭遇强降雨后&#xff0c;地面积水过多&#xff0c;导致城市交通堵塞、居民生活不便、财产损失等问题。近年来&#xff0c;随着全球气候变化和城市化进程的加速&#xff0c;城市内涝积水问题越来越突出&#xff0c;成为城市发展中的一大…...

编译正点原子LINUXB报错make: arm-linux-gnueabihf-gcc:命令未找到

编译正点原子LINUX报错make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到 1.报错内容2.解决办法3./bin/sh: 1: lzop: not found4.编译成功 1.报错内容 make: arm-linux-gnueabihf-gcc&#xff1a;命令未找到CHK include/config/kernel.releaseCHK include/generat…...

工地现场智慧管理信息化解决方案 智慧工地源码

智慧工地系统充分利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术&#xff0c;以PC端&#xff0c;移动端&#xff0c;设备端三位一体的管控方式为企业现场工程管理提供了先进的技术手段。让劳务、设备、物料、安全、环境、能源、资料、计划、质量、视频监控等…...

Javaweb之HTML,CSS的详细解析

2. HTML & CSS 1). 什么是HTML ? HTML: HyperText Markup Language&#xff0c;超文本标记语言。 超文本&#xff1a;超越了文本的限制&#xff0c;比普通文本更强大。除了文字信息&#xff0c;还可以定义图片、音频、视频等内容。 标记语言&#xff1a;由标签构成的语言…...

基于python+django+vue开发的酒店预订管理系统 - 毕业设计 - 课程设计

文章目录 源码下载地址项目介绍项目功能界面预览项目备注毕设定制&#xff0c;咨询 源码下载地址 点击这里下载源码 项目介绍 该系统是基于pythondjango开发的酒店预定管理系统。适用场景&#xff1a;大学生、课程作业、毕业设计。学习过程中&#xff0c;如遇问题可在github…...

使用vscode实现远程开发,并通过内网穿透在公网环境下远程连接

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…...

ArrayList集合2

ArrayList集合的一些方法 ⑥chear()从列表中移除所有元素 ⑦.isEmpty()判断列表中是否包含元素&#xff0c;不包含返回true&#xff0c;否则返回false public class Test{public static void main(String[] args){Arraylist<String> list new Arraylist<String>()…...

vue+asp.net Web api前后端分离项目发布部署

一、前后端项目介绍 1.前端项目是使用vue脚手架进行创建的。 脚手架版本&#xff1a;vue/cli 5.0.8 编译器版本&#xff1a;vs code 1.82.2 2.后端是一个asp.net Core Web API 项目 后端框架版本&#xff1a;.NET 6.0 编译器版本&#xff1a;vs 2022 二、发布部署步骤 第…...

iOS App Store上传项目报错 缺少隐私政策网址(URL)解决方法

​ iOS App Store上传项目报错 缺少隐私政策网址(URL)解决方法 一、问题如下图所示&#xff1a; ​ 二、解决办法&#xff1a;使用Google浏览器&#xff08;翻译成中文&#xff09;直接打开该网址 https://www.freeprivacypolicy.com/free-privacy-policy-generator.php 按…...

如何使用Ruby 多线程爬取数据

现在比较主流的爬虫应该是用python&#xff0c;之前也写了很多关于python的文章。今天在这里我们主要说说ruby。我觉得ruby也是ok的&#xff0c;我试试看写了一个爬虫的小程序&#xff0c;并作出相应的解析。 Ruby中实现网页抓取&#xff0c;一般用的是mechanize&#xff0c;使…...

一文深入了解 CPU 的型号、代际架构与微架构

在 10 月 16 号的时候&#xff0c;Intel 正式发布了第 14 代的酷睿处理器。但还有很多同学看不懂这种发布会上发布的各种 CPU 参数。借着这个时机&#xff0c;给大家深入地讲讲 CPU 的型号规则、代际架构与微架构方面的知识。 CPU 在整个计算机硬件中、技术体系中都算是最最重…...

Java通过cellstyle属性设置Excel单元格常用样式全面总结

最近做了一个导出Excel的功能&#xff0c;导出是个常规导出&#xff0c;但是拿来模板一看&#xff0c;有一些单元格的样式设置&#xff0c;包括合并&#xff0c;背景色&#xff0c;字体等等&#xff0c;毕竟不是常用的东西&#xff0c;需要查阅资料完成&#xff0c;但是搜遍全网…...

如何查看WiFi密码

本文分享一下手机和电脑上如何查看已经连接过的WiFi的密码&#xff0c;然后好分享给他人。 手机上分享wifi密码很简单&#xff0c;步骤如下&#xff1a; 生成二维码&#xff0c;读取WiFi密码 1、首先&#xff0c;在“设置”中找到“无线网络”&#xff0c;点击需要查找密码的Wi…...

2023NOIP A层联测22 总结

T1 简单分析了性质&#xff0c;发现可以用双指针求值&#xff0c;后面又发现可以用类似于线段树求最大子段和的方式维护。用时 40min T2 是期望&#xff0c;想了 30min 没有思路&#xff0c;于是打暴力&#xff0c;但是打的时候没有想清楚&#xff0c;就打了很久&#xff0c;大…...

HTTPS的加密方式超详细解读

在了解https的加密方式之前&#xff0c;我们需要先行了解两个特别经典的传统加密方式&#xff1a; 1、对称加密 1.1、定义 需要对加密和解密使用相同密钥的加密算法。所谓对称&#xff0c;就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解…...

自定义SpringMVC拦截器,实现内外网访问控制功能

这篇文章简单介绍如何自定义一个SpringMVC拦截器&#xff0c;并通过拦截器实现具体的功能。 首先&#xff0c;需要创建一个自定义的拦截器类&#xff0c;该类实现HandlerInterceptor接口。 package cn.edu.sgu.www.mhxysy.interceptor;import cn.edu.sgu.www.mhxysy.feign.Fei…...

在pycharm中配置GPU训练环境(Anaconda)(yolov5)

目录 1. 具体的配置过程&#xff1a; 2. 在指定位置&#xff08;路径&#xff09;创建虚拟环境&#xff1a; 3. conda常用命令&#xff1a; 4: 在跑模型时候遇到的一些问题&#xff1a; 4.1: conda添加python解释器找不到对应的python.exe文件 4.2: 报错“OSError: [WinErr…...

【LeetCode刷题-链表】--146.LRU缓存

146.LRU缓存 方法一&#xff1a;哈希表双向链表 使用一个哈希表和一个双向链表维护所有在缓存中的键值对 双向链表按照被使用的顺序存储了这些键值对&#xff0c;靠近头部的键值对是最近使用的&#xff0c;而靠近尾部的键值对是最久使用的哈希表即为普通的哈希映射&#xff0…...

mysql 问题解答

01 Mysql有哪些数据类型 MySQL支持多种数据类型,这些类型可以分为几个大的类别:数值类型、日期和时间类型、字符串(字符和字节)类型、空间类型、JSON类型。下面是每种类型的简要说明和用途,以及示例。 数值类型 整型: TINYINT:非常小的整数,如性别标识(0代表女性,1代…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...