当前位置:   article > 正文

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

小程序 圆形滑块

效果:

功能点:

1、四个档位

2、可点击加减切换档位

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

4、可以滑动切换档位

目的:

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

代码

ts的写法风格

index.tsx     

  1. import { View, ITouchEvent, BaseTouchEvent } from '@tarojs/components'
  2. import Taro from '@tarojs/taro'
  3. import { useState } from 'react'
  4. import styles from './index.module.less'
  5. import classNames from 'classnames'
  6. import { debounce } from '~/utils/util'
  7. enum ANGLES {
  8. ANGLES_135 = -135,
  9. ANGLES_90 = -90,
  10. ANGLES_45 = -45,
  11. ANGLES_0 = 0
  12. }
  13. enum MODE_VALUE {
  14. MODE_1 = 1,
  15. MODE_2 = 2,
  16. MODE_3 = 3,
  17. MODE_4 = 4
  18. }
  19. const HalfCircle = () => {
  20. const [state, setState] = useState({
  21. originAngle: ANGLES.ANGLES_135,
  22. isTouch: false,
  23. val: MODE_VALUE.MODE_1,
  24. originX: 0,
  25. originY: 0
  26. })
  27. /** 半圆的半径 */
  28. const RADIUS = 150
  29. /** 半径的一半 */
  30. const RADIUS_HALF = RADIUS / 2
  31. /** 4/3 圆的直径 */
  32. const RADIUS_THIRD = RADIUS_HALF * 3
  33. /** 直径 */
  34. const RADIUS_DOUBLE = RADIUS * 2
  35. /** 误差 */
  36. const DEVIATION = 25
  37. /** 是否开启点击振动 */
  38. const isVibrateShort = true
  39. const getAngle = () => {
  40. return {
  41. transform: `rotate(${state.originAngle}deg)`,
  42. transition: `all ${state.isTouch ? ' 0.2s' : ' 0.55s'}`
  43. }
  44. }
  45. /**
  46. * 根据坐标判断是否在半圆轨道上,半圆为RADIUS,误差为DEVIATION
  47. * @param pageX
  48. * @param pageY
  49. */
  50. const isInHalfCircleLine = (pageX: number, pageY: number, deviation?: number) => {
  51. const DEVIATION_VALUE = deviation || DEVIATION
  52. const squareSum = (pageX - RADIUS) * (pageX - RADIUS) + (pageY - RADIUS) * (pageY - RADIUS)
  53. const min = (RADIUS - DEVIATION_VALUE) * (RADIUS - DEVIATION_VALUE)
  54. const max = (RADIUS + DEVIATION_VALUE) * (RADIUS + DEVIATION_VALUE)
  55. return squareSum >= min && squareSum <= max
  56. }
  57. /** 根据做标点,获取档位 0 -> 4, -45 -> 3, -90 -> 2, -135 -> 1,从而获取旋转的角度 */
  58. const setGear = (pageX: number, pageY: number) => {
  59. let val = state.val
  60. let originAngle = state.originAngle
  61. if (isInHalfCircleLine(pageX, pageY)) {
  62. if (pageX > 0 && pageX <= RADIUS_HALF) {
  63. val = MODE_VALUE.MODE_1
  64. originAngle = ANGLES.ANGLES_135
  65. } else if (pageX > RADIUS_HALF && pageX <= RADIUS) {
  66. val = MODE_VALUE.MODE_2
  67. originAngle = ANGLES.ANGLES_90
  68. } else if (pageX > RADIUS && pageX <= RADIUS_THIRD) {
  69. val = MODE_VALUE.MODE_3
  70. originAngle = ANGLES.ANGLES_45
  71. } else {
  72. val = MODE_VALUE.MODE_4
  73. originAngle = ANGLES.ANGLES_0
  74. }
  75. }
  76. if (state.val === val) return
  77. setState((old) => {
  78. return {
  79. ...old,
  80. originAngle,
  81. val
  82. }
  83. })
  84. if (isVibrateShort) {
  85. setTimeout(() => {
  86. Taro.vibrateShort()
  87. }, 200)
  88. }
  89. }
  90. /**
  91. * 滑动比较细腻,根据x轴坐标,calcX判断是否前进还是后退
  92. * @param pageX
  93. * @param pageY
  94. */
  95. const setGearSibler = (pageX: number, pageY: number) => {
  96. let val = state.val
  97. let originAngle = state.originAngle
  98. const calcX = pageX - state.originX
  99. /** 把误差值增加,方便滑动 */
  100. if (isInHalfCircleLine(pageX, pageY, 50)) {
  101. if (pageX > 0 && pageX <= RADIUS_HALF) {
  102. if (calcX > 0) {
  103. /** 向前滑动,就前进一个档位 */
  104. val = MODE_VALUE.MODE_2
  105. originAngle = ANGLES.ANGLES_90
  106. } else {
  107. /** 向后滑动,就后退一个档位 */
  108. val = MODE_VALUE.MODE_1
  109. originAngle = ANGLES.ANGLES_135
  110. }
  111. } else if (pageX > RADIUS_HALF && pageX <= RADIUS) {
  112. if (calcX > 0) {
  113. val = MODE_VALUE.MODE_2
  114. originAngle = ANGLES.ANGLES_90
  115. } else {
  116. val = MODE_VALUE.MODE_1
  117. originAngle = ANGLES.ANGLES_135
  118. }
  119. } else if (pageX > RADIUS && pageX <= RADIUS_THIRD) {
  120. if (calcX > 0) {
  121. val = MODE_VALUE.MODE_3
  122. originAngle = ANGLES.ANGLES_45
  123. } else {
  124. val = MODE_VALUE.MODE_2
  125. originAngle = ANGLES.ANGLES_90
  126. }
  127. } else {
  128. if (calcX > 0) {
  129. val = MODE_VALUE.MODE_4
  130. originAngle = ANGLES.ANGLES_0
  131. } else {
  132. val = MODE_VALUE.MODE_3
  133. originAngle = ANGLES.ANGLES_45
  134. }
  135. }
  136. }
  137. setState((old) => {
  138. return {
  139. ...old,
  140. originAngle,
  141. val
  142. }
  143. })
  144. }
  145. /**
  146. * 获取正确的坐标点
  147. * @param pageX
  148. * @param pageY
  149. * @returns
  150. */
  151. const getRealXY = (
  152. pageX: number,
  153. pageY: number
  154. ): Promise<{
  155. realX: number
  156. realY: number
  157. }> => {
  158. return new Promise((resolve) => {
  159. Taro.createSelectorQuery()
  160. .select('#sliderBgcId')
  161. .boundingClientRect((rect) => {
  162. const { left, top } = rect
  163. /** 获取真实的做标点 */
  164. const realX = pageX - left
  165. const realY = pageY - top
  166. resolve({
  167. realX,
  168. realY
  169. })
  170. })
  171. .exec()
  172. })
  173. }
  174. const onTouchEnd = (event: BaseTouchEvent<any>) => {
  175. setState((old) => {
  176. return {
  177. ...old,
  178. isTouch: false
  179. }
  180. })
  181. }
  182. const onTouchMove = debounce(async (event: BaseTouchEvent<any>) => {
  183. const { pageX, pageY } = event.changedTouches[0]
  184. const { realX, realY } = await getRealXY(pageX, pageY)
  185. if (isInHalfCircleLine(realX, realY)) {
  186. setGearSibler(realX, realY)
  187. }
  188. }, 100)
  189. const onTouchStart = async (event: BaseTouchEvent<any>) => {
  190. const { pageX, pageY } = event.changedTouches[0]
  191. const { realX, realY } = await getRealXY(pageX, pageY)
  192. setState((old) => {
  193. return {
  194. ...old,
  195. originX: realX,
  196. originY: realY,
  197. isTouch: true
  198. }
  199. })
  200. }
  201. /** 点击设置档位 */
  202. const onHandleFirstTouch = async (event: BaseTouchEvent<any>) => {
  203. const { pageX, pageY } = event.changedTouches[0]
  204. const { realX, realY } = await getRealXY(pageX, pageY)
  205. if (isInHalfCircleLine(realX, realY)) {
  206. setGear(realX, realY)
  207. }
  208. }
  209. const lose = () => {
  210. if (state.isTouch) return
  211. if (state.val === 1) return Taro.showToast({
  212. title: '最低只能1挡',
  213. icon: 'error',
  214. duration: 2000
  215. })
  216. setState((old) => {
  217. return {
  218. ...old,
  219. originAngle: state.originAngle - 45,
  220. val: state.val - 1
  221. }
  222. })
  223. if (isVibrateShort) {
  224. Taro.vibrateShort()
  225. }
  226. }
  227. const add = () => {
  228. if (state.isTouch) return
  229. if (state.val === 4) return Taro.showToast({
  230. title: '最高只能4挡',
  231. icon: 'error',
  232. duration: 2000
  233. })
  234. setState((old) => {
  235. return {
  236. ...old,
  237. originAngle: state.originAngle + 45,
  238. val: state.val + 1
  239. }
  240. })
  241. if (isVibrateShort) {
  242. Taro.vibrateShort()
  243. }
  244. }
  245. return (
  246. <View
  247. className={styles.slider}
  248. // onTouchEnd={(event) => onTouchEnd(event)}
  249. // onTouchMove={(event) => onTouchMove(event)}
  250. // onTouchStart={(event) => onTouchStart(event)}
  251. onClick={onHandleFirstTouch}
  252. >
  253. <View className={styles.activeSliderSet}>
  254. <View className={styles.activeSlider} style={getAngle()} />
  255. </View>
  256. <View className={styles.origin} id="origin">
  257. <View className={styles.long} style={getAngle()}>
  258. <View
  259. className={styles.circle}
  260. onTouchMove={(event) => onTouchMove(event as BaseTouchEvent<any>)}
  261. onTouchStart={(event) => onTouchStart(event as BaseTouchEvent<any>)}
  262. onTouchEnd={(event) => onTouchEnd(event as BaseTouchEvent<any>)}
  263. />
  264. </View>
  265. </View>
  266. {/* 背景 */}
  267. <View className={styles.sliderBgc} id="sliderBgcId" />
  268. {/* 刻度 */}
  269. {/* <View className={styles.scaleBgc} /> */}
  270. <View className={styles.centerContent}>
  271. <View className={styles.centerText}>能量档位</View>
  272. <View className={styles.btn_air_bar}>
  273. <View className={classNames(styles.btn_air, styles.btn_air_left)} onClick={lose}>
  274. -
  275. </View>
  276. <View className={styles.val}>
  277. <View className="val_text">{state.val}</View>
  278. </View>
  279. <View className={classNames(styles.btn_air, styles.btn_air_right)} onClick={add}>
  280. +
  281. </View>
  282. </View>
  283. </View>
  284. </View>
  285. )
  286. }
  287. export default HalfCircle

index.module.less

  1. @color-brand: #EBC795 ;
  2. @borderColor:#706D6D;
  3. @sliderWidth:10px;
  4. @radius:150px;
  5. @long: 150px;
  6. @border-radius: @long;
  7. .slider {
  8. position: relative;
  9. padding-bottom: @sliderWidth / 2;
  10. background-color: #000;
  11. width: 100vw;
  12. display: flex;
  13. justify-content: center;
  14. align-items: center;
  15. // 背景色
  16. .sliderBgc {
  17. width: @long*2;
  18. height: @long;
  19. border: @sliderWidth solid;
  20. border-radius: @border-radius @border-radius 0 0;
  21. border-color: @borderColor;
  22. border-bottom: none;
  23. }
  24. .scaleBgc {
  25. width: @long*2 + @sliderWidth *2;
  26. height: @long + @sliderWidth;
  27. position: absolute;
  28. // bottom: 0;
  29. // left: 0;
  30. border: @sliderWidth solid;
  31. border-radius: @border-radius + @sliderWidth @border-radius + @sliderWidth 0 0;
  32. border-color: transparent;
  33. border-bottom: none;
  34. top: -10px;
  35. background-clip: padding-box, border-box;
  36. background-origin: padding-box, border-box;
  37. background-image: linear-gradient(to right, #000, #000), linear-gradient(90deg, #FFD1B2, #E49E6B);
  38. }
  39. // 激活色
  40. .activeSliderSet {
  41. position: absolute;
  42. width: (@long) *2;
  43. height: @long;
  44. // left: 0;
  45. // bottom: 0;
  46. z-index: 2;
  47. overflow: hidden;
  48. .activeSlider {
  49. bottom: 0;
  50. left: 0;
  51. width: @long*2;
  52. height: @long;
  53. border: @sliderWidth solid;
  54. border-color: @color-brand;
  55. // border-color: transparent !important;
  56. border-radius: @border-radius @border-radius 0 0;
  57. border-bottom: none;
  58. transform: rotate(-100deg);
  59. transform-origin: @long @long;
  60. // background-clip: padding-box, border-box;
  61. // background-origin: padding-box, border-box;
  62. // background-image: linear-gradient(to right, #000, #000), linear-gradient(90deg, #FFD1B2, #E49E6B);
  63. }
  64. }
  65. .origin {
  66. width: 0;
  67. height: 0;
  68. position: absolute;
  69. background-color: rgba(0, 0, 0, 0.1);
  70. bottom: 0;
  71. left: 50%;
  72. z-index: 11;
  73. transform: translateX(50%);
  74. .long {
  75. width: @long - (@sliderWidth / 2);
  76. height: 0;
  77. z-index: 9999;
  78. position: absolute;
  79. top: 0;
  80. left: 0;
  81. transform-origin: 0 0;
  82. .circle {
  83. width: 16px;
  84. height: 16px;
  85. border-radius: 50%;
  86. position: absolute;
  87. top: 50%;
  88. right: 0;
  89. transform: translate(50%, -50%);
  90. background-color: #000;
  91. border: #fff 4px solid;
  92. z-index: 999;
  93. padding: 5px;
  94. }
  95. }
  96. }
  97. }
  98. .centerContent {
  99. position: absolute;
  100. bottom: 0;
  101. left: 50%;
  102. transform: translateX(-50%);
  103. z-index: 99;
  104. margin-bottom: 20px;
  105. .centerText {
  106. text-align: center;
  107. color: var(--q-light-color-text-secondary, var(--text-secondary, #8C8C8C));
  108. font-size: 10px;
  109. margin-bottom: 25px;
  110. }
  111. .btn_air_bar {
  112. display: flex;
  113. align-items: center;
  114. .btn_air {
  115. width: 30px;
  116. height: 30px;
  117. border-radius: 50%;
  118. background-color: wheat;
  119. font-size: 16px;
  120. font-weight: 500;
  121. display: flex;
  122. align-items: center;
  123. justify-content: center;
  124. }
  125. .btn_air_left {
  126. background-color: #706D6D;
  127. color: white;
  128. }
  129. .btn_air_right {
  130. background-color: white;
  131. color: #706D6D;
  132. }
  133. .val {
  134. height: 26px;
  135. display: flex;
  136. align-items: center;
  137. margin: 0 30px;
  138. font-size: 26px;
  139. font-weight: 700;
  140. }
  141. }
  142. }

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

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

  1. function debounce<T extends Function>(func: T, delay: number): T {
  2. let timeout
  3. return function (this: any, ...args: any[]): void {
  4. const context = this;
  5. clearTimeout(timeout);
  6. timeout = setTimeout(() => {
  7. func.apply(context, args);
  8. }, delay);
  9. } as any;
  10. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/146938
推荐阅读
相关标签
  

闽ICP备14008679号