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) => {
setState((old) => {
return {
...old,
isTouch: false
}
})
}
const onTouchMove = debounce(async (event: BaseTouchEvent) => {
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) => {
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) => {
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 (
onTouchEnd(event)}
// onTouchMove={(event) => onTouchMove(event)}
// onTouchStart={(event) => onTouchStart(event)}
onClick={onHandleFirstTouch}
>
onTouchMove(event as BaseTouchEvent)}
onTouchStart={(event) => onTouchStart(event as BaseTouchEvent)}
onTouchEnd={(event) => onTouchEnd(event as BaseTouchEvent)}
/>
{/* 背景 */}
{/* 刻度 */}
{/* */}
能量档位
-
{state.val}
+
)
}
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(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;
}