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

本文主要是介绍基于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 实现微信小程序半圆滑块组件、半圆进度条、弧形进度条、半圆滑行轨道(附源码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/322952

相关文章

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间

全面解析HTML5中Checkbox标签

《全面解析HTML5中Checkbox标签》Checkbox是HTML5中非常重要的表单元素之一,通过合理使用其属性和样式自定义方法,可以为用户提供丰富多样的交互体验,这篇文章给大家介绍HTML5中C... 在html5中,Checkbox(复选框)是一种常用的表单元素,允许用户在一组选项中选择多个项目。本

HTML5 搜索框Search Box详解

《HTML5搜索框SearchBox详解》HTML5的搜索框是一个强大的工具,能够有效提升用户体验,通过结合自动补全功能和适当的样式,可以创建出既美观又实用的搜索界面,这篇文章给大家介绍HTML5... html5 搜索框(Search Box)详解搜索框是一个用于输入查询内容的控件,通常用于网站或应用程

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1