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

效果:

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

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

功能点:

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;
}

你可能感兴趣的:(Taro,一些例子,taro,react.js,前端,半圆滑轨,半圆进度条,弧形进度条,半圆滑块组件)