撸一个简易大转盘

1,248 阅读3分钟

今天产品需要 要求把原来的活动功能改成大转盘形式的 于是乎我也是第一次做这种需求 就动手撸了一个

需求分析

  • 第一想法既然是转盘 原地转那当然想到了css3transform
  • 动画实现了 那转盘上对应的刻度对应展示如何实现呢? 我想到的是平移 各种x,y,z轴。但用的毕竟还是不是非常多 于是我又去复习了一下transition的各种属性
  • 如果想要做成那种异形状的样子 可以用clip裁剪
  • 差不多动画已经没有什么难点了 那就动手开始撸了

因为最近在搭react 18.9+webpack4.x+less等等各种的架构(也还在加深学习hook) 所以顺手就用react写的 如果有需要 其实我也有vue版 差不太多的

上代码 (基本样式 这里是用的less写的)

.project {
  overflow: hidden;
  &-content {
    padding-top: 5rem;
    position: relative;
    img {
      width: 60px;
      height: 60px;
      transform: translateY(-8px) translateX(-30px);
      position: absolute;
      left: 50%;
      top: 50%;
      z-index: 10
    }
    &-box {
      position: relative;
      background: url('~@/assets/draw_bg.png') no-repeat;
      background-size: 100% 100%;
      width: 300px;
      height: 300px;
      margin: auto;
      transition: all 5s ease;
      &-item {
        position: absolute;
        font-size: 16px;
        height: 150px;
        width: 40px;
        display: flex;
        align-items: center;
        justify-content: center;
        top: 0;
        left: 0;
        margin: auto;
        right: 0;
        transform-origin: 20px 150px;
        &.li0 {
          transform: rotate(22.5deg)
        }
        &.li1 {
          transform: rotate(67.5deg)
        }
        &.li2 {
          transform: rotate(112.5deg)
        }
        &.li3 {
          transform: rotate(157.5deg)
        }
        &.li4 {
          transform: rotate(202.5deg)
        }
        &.li5 {
          transform: rotate(247.5deg)
        }
        &.li6 {
          transform: rotate(292.5deg)
        }
        &.li7 {
          transform: rotate(337.5deg)
        }
      }
    }
  }
}

transition我自己选用的是ease的动画方式开始慢中间快结束慢 有需要也可以手写速度

对应的每一个刻度的角度 下面的注释里有讲 应该很好理解

主要用到了transform-origin进行的平移rotate旋转 这边就不讲细节了

下面就是核心代码

import React, { useEffect, useRef, useState } from 'react'
import './style.less'
import { Modal } from 'antd';

export default () => {
  const [timer, setTimer] = useState(null)
  const box = useRef(null)

  useEffect(() => {
  // 清除计时 我有强迫症 请忽略
    return () => clearTimeout(timer)
  }, [timer])
  
  const handle = () => {
    // 起始圈数
    const speed = 7
    // 拿到服务端返回的奖项索引
    const index = 4
    // 这里的45度 因为这里是按八块一圈 360 / 8 - 45 * 刻度 加上22.5是因为对应的刻度要加上45每格的一半(顺时针如此)
    const _deg = speed * 360 - index * 45 + 22.5
    // 开始动画
    box.current.style.transition = 'all 5s ease'
    box.current.style.transform = `rotate(${_deg}deg)`
    // 方式多次重复点击
    if (timer) { return }
    setTimer(setTimeout(() => {
      Modal.success({
        title: '领取奖励',
        content: '确认',
        onOk: () => new Promise((resolve, reject) => {
          // 清空动画
          Object.assign(box.current.style, {
            transform: '',
            transition: 'none'
          })
          resolve()
        })
      })
      // 清除计时器修改时间为null方便上面验证
      clearTimeout(timer)
      setTimer(null)
    }, 5000))
  }

  return (
    <div className='project'>
      <div className="project-content">
        <img onClick={handle} src={require('@/assets/draw_btn.png')} alt=""/>
        <div ref={box} className="project-content-box">
          {
            [...Array(8).keys()].map((n) => {
              return (
                <div key={n} className={'project-content-box-item '+`li${n}`}>{n}</div>
              )
            })
          }
        </div>
      </div>
    </div>
  )
}

这里是用的ref操作的dom节点样式 antd的确认弹框组件做的确认回调

至于清空样式的时机 可以自己拓展 当然也可以不清空 在原有基础上开始转 那样deg的数值就是在原有的基础上加了(因为如果直接覆盖的话 动画效果会在原有基础上去转动 会有问题) 具体算的话需要计算原来所在的刻度 然后加上所缺然后补齐到0然后再进行刻度和圈数的增加

为了节约时间 点击触发没有做详细防抖 做了一个简易的验证 是那个意思

speed index对应奖项这些可以对应业务需求去拓展 我这边就不多说了

其实真的自己动手做起来发现其实并没有什么难点 记录来玩玩

如果真的有人需要vue的版本的话 可以评论我发给你 我就不帖在这了