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 = true
-
- const 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 || DEVIATION
- const 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.val
- let originAngle = state.originAngle
- if (isInHalfCircleLine(pageX, pageY)) {
- if (pageX > 0 && pageX <= RADIUS_HALF) {
- val = MODE_VALUE.MODE_1
- originAngle = ANGLES.ANGLES_135
- } else if (pageX > RADIUS_HALF && pageX <= RADIUS) {
- val = MODE_VALUE.MODE_2
- originAngle = ANGLES.ANGLES_90
- } else if (pageX > RADIUS && pageX <= RADIUS_THIRD) {
- val = MODE_VALUE.MODE_3
- originAngle = ANGLES.ANGLES_45
- } else {
- val = MODE_VALUE.MODE_4
- originAngle = ANGLES.ANGLES_0
- }
- }
-
- if (state.val === val) return
- setState((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.val
- let originAngle = state.originAngle
- const calcX = pageX - state.originX
- /** 把误差值增加,方便滑动 */
- if (isInHalfCircleLine(pageX, pageY, 50)) {
- if (pageX > 0 && pageX <= RADIUS_HALF) {
- if (calcX > 0) {
- /** 向前滑动,就前进一个档位 */
- val = MODE_VALUE.MODE_2
- originAngle = ANGLES.ANGLES_90
- } else {
- /** 向后滑动,就后退一个档位 */
- val = MODE_VALUE.MODE_1
- originAngle = ANGLES.ANGLES_135
- }
- } else if (pageX > RADIUS_HALF && pageX <= RADIUS) {
- if (calcX > 0) {
- val = MODE_VALUE.MODE_2
- originAngle = ANGLES.ANGLES_90
- } else {
- val = MODE_VALUE.MODE_1
- originAngle = ANGLES.ANGLES_135
- }
- } else if (pageX > RADIUS && pageX <= RADIUS_THIRD) {
- if (calcX > 0) {
- val = MODE_VALUE.MODE_3
- originAngle = ANGLES.ANGLES_45
- } else {
- val = MODE_VALUE.MODE_2
- originAngle = ANGLES.ANGLES_90
- }
- } else {
- if (calcX > 0) {
- val = MODE_VALUE.MODE_4
- originAngle = ANGLES.ANGLES_0
- } else {
- val = MODE_VALUE.MODE_3
- originAngle = ANGLES.ANGLES_45
- }
- }
- }
- setState((old) => {
- return {
- ...old,
- originAngle,
- val
- }
- })
- }
-
- /**
- * 获取正确的坐标点
- * @param pageX
- * @param pageY
- * @returns
- */
- const getRealXY = (
- pageX: number,
- pageY: number
- ): Promise<{
- realX: number
- realY: number
- }> => {
- return new Promise((resolve) => {
- Taro.createSelectorQuery()
- .select('#sliderBgcId')
- .boundingClientRect((rect) => {
- const { left, top } = rect
- /** 获取真实的做标点 */
- const realX = pageX - left
- const realY = pageY - top
- resolve({
- 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) return
- if (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) return
- if (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 (
- <View
- className={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()}>
- <View
- className={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
extends Function>(func: T, delay: number): T { - let timeout
- return function (this: any, ...args: any[]): void {
- const context = this;
- clearTimeout(timeout);
- timeout = setTimeout(() => {
- func.apply(context, args);
- }, delay);
- } as any;
- }