基于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.rhkb.cn/news/176688.html

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

相关文章

Kubernetes - Ingress HTTP 升级 HTTPS 配置解决方案(新版本v1.21+)

之前我们讲解过 Kubernetes - Ingress HTTP 搭建解决方案&#xff0c;并分别提供了旧版本和新版本。如果连 HTTP 都没搞明白的可以先去过一下这两篇 Kubernetes - Ingress HTTP 负载搭建部署解决方案_放羊的牧码的博客-CSDN博客Kubernetes - Ingress HTTP 负载搭建部署解决方案…

SpringMVC Day 07 : 表单验证

前言 表单验证在Web开发中是非常常见和重要的一部分&#xff0c;它用于确保用户提交的数据符合预期的规则和限制。 通过表单验证&#xff0c;我们可以有效地捕获并处理用户输入中的错误或不正确的数据&#xff0c;从而提高应用程序的数据质量和用户体验。在本教程中&#xff…

Thread

Thread 线程启动线程第一种创建线程线程的第二种创建方式使用匿名内部类完成线程的两种创建 Thread API线程的优先级线程提供的静态方法守护线程用户线程和守护线程的区别体现在进程结束时 多线并发安全问题同步块 线程 启动线程 启动线程:调用线程的start方法,而不是直接调用…

lua-resty-request库写入爬虫ip实现数据抓取

根据提供的引用内容&#xff0c;正确的库名称应该是lua-resty-http&#xff0c;而不是lua-resty-request。使用lua-resty-http库可以方便地进行爬虫&#xff0c;需要先安装OpenResty和lua-resty-http库&#xff0c;并将其引入到Lua脚本中。然后&#xff0c;可以使用lua-resty-h…

【网络协议】聊聊http协议

当我们输入www.baidu.com的时候&#xff0c;其实是先将baidu.com的域名进行DNS解析&#xff0c;转换成对应的ip地址&#xff0c;然后开始进行基于TCP构建三次握手的连接&#xff0c;目前使用的是1.1 默认是开启了keep-Alive。可以在多次请求中进行连接复用。 HTTP 请求的构建…

【k8s】pod详解

一、Pod介绍 1、Pod的基础概念 Pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象&#xff0c;一个pod代表着集群中运行的一个进程。kubernetes中其它大多数组件都是围绕着pod来进行支持和扩展pod功能的。 例如&#xff0c;用于管理po…

计算机基础知识41

前端 # 前端是所有跟用户直接打交道 比如&#xff1a;PC页面、手机页面、汽车显示屏&#xff0c;肉眼可以看见的 # 后端&#xff1a;一堆代码&#xff0c;用户不能够直接看到&#xff0c;不直接与用户打交道 常见的后端&#xff1a;Python、Java、Go等 # 学了前端就可以做全栈…

算法题:二叉树的层序遍历

层序遍历&#xff0c;看似简单&#xff0c;实则陷阱很多&#xff0c;怪不得该题目被认定为中等难度题。此处运用了迭代求解法。&#xff08;完整题目附在了最后&#xff09; # Definition for a binary tree node. class TreeNode:def __init__(self, val0, leftNone, rightNon…

Vectrosity 插件使用

1 下载 https://download.csdn.net/download/moonlightpeng/88490704?spm1001.2014.3001.5503 2 使用&#xff0c;目前在2020.3.3上测试可以 导入时选5.6 再导入demo

FlinkCDC系列:通过skipped.operations参数选择性处理新增、更新、删除数据

在flinkCDC源数据配置&#xff0c;通过debezium.skipped.operations参数控制&#xff0c;配置需要过滤的 oplog 操作。操作包括 c 表示插入&#xff0c;u 表示更新&#xff0c;d 表示删除。默认情况下&#xff0c;不跳过任何操作&#xff0c;以逗号分隔。配置多个操作&#xff…

四、[mysql]索引优化-1

目录 前言一、场景举例1.联合索引第一个字段用范围查询不走索引(分情况&#xff09;2.强制走指定索引3.覆盖索引优化4.in和or在表数据量比较大的情况会走索引&#xff0c;在表记录不多的情况下会选择全表扫描5.like 后% 一般情况都会走索引(索引下推) 二、Mysql如何选择合适的索…

(三)上市企业实施IPD成功案例分享之——五菱

2022年对汽车产业而言是极为不平凡的一年。这一年&#xff0c;企业受到疫情反复、芯片短缺、原材料价格上涨等负面因素影响&#xff0c;汽车产业的变革持续加速。面对变革与挑战&#xff0c;五菱汽车仍逆势交出一份超出市场预期的成绩单。上半年&#xff0c;五菱发布2022年财报…

2023年【熔化焊接与热切割】考试题及熔化焊接与热切割模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【熔化焊接与热切割】考试题及熔化焊接与热切割模拟考试&#xff0c;包含熔化焊接与热切割考试题答案和解析及熔化焊接与热切割模拟考试练习。安全生产模拟考试一点通结合国家熔化焊接与热切割考试最新大纲及熔…

ASO优化之如何制作Google Play的长短描述

应用的描述以及标题和图标是元数据中最关键的元素&#xff0c;可以影响用户是否决定下载我们的应用程序。简短描述的长度限制为80个字符&#xff0c;它提供了更多的有关应用背景信息的机会。 1、简短描述帮助用户快速了解我们应用。 确保内容丰富的同时&#xff0c;保持简洁和…

Redis与Mysql的数据一致性(双写一致性)

双写一致性&#xff1a;当修改了数据库的数据也要同时的更新缓存的数据&#xff0c;使缓存和数据库的数据要保持一致。 一般是在写数据的时候添加延迟双删的策略 先删缓存 再修改数据 延迟一段时间后再次删除缓存 这种方式其实不是很靠谱 一致性要求高 共享锁&#xff1a;读…

Android裁剪图片之后无法加载的问题

适配Android11之后更改了图片保存目录&#xff0c;导致裁剪之后图片一直无法加载&#xff08;fileNotfound&#xff09; 最主要的问题在于保存裁剪文件的目录不能为私有目录&#xff0c;因为裁剪工具是系统工具&#xff0c;无法直接访问项目本身的私有目录。 解决办法&#x…

基于适应度相关算法的无人机航迹规划-附代码

基于适应度相关算法的无人机航迹规划 文章目录 基于适应度相关算法的无人机航迹规划1.适应度相关搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用适应度相关算法来优化无人机航迹…

分布式:一文吃透分布式事务和seata事务

目录 一、事务基础概念二、分布式事务概念什么是分布式事务分布式事务场景CAP定理CAP理论理解CAPCAP的应用 BASE定理强一致性和最终一致性BASE理论 分布式事务分类刚性事务柔性事务 三、分布式事务解决方案方案汇总XA规范方案1&#xff1a;2PC第一阶段&#xff1a;准备阶段第二…

阿里云急了,云服务器老用户优惠价格99元一年!

2023阿里云服务器双11优惠价格99元一年经济型e实例&#xff0c;并且续费不涨价&#xff0c;云服务器ECS-经济型e实例2核2G配置、3M带宽、40G ESSD entry系统盘优惠价99元一年&#xff0c;原价956.64元/年&#xff0c;可用于中小型网站建设、开发测试、小程序或app搭建&#xff…

用rust写web服务器笔记(11/1)

文章目录 一、创建一个具有监听链接功能web服务器二、读取请求内容三、编写web服务器返回网页(编写响应)四、编写web服务器有条件的返回网页五、编写多线程的web服务器六、用线程池实现web服务器七、实现线程池清除的web服务器八、读取文件 rust官网文档地址&#xff1a;https:…