今天产品需要 要求把原来的活动功能改成大转盘形式的 于是乎我也是第一次做这种需求 就动手撸了一个
需求分析
- 第一想法既然是转盘 原地转那当然想到了
css3的transform - 动画实现了 那转盘上对应的刻度对应展示如何实现呢? 我想到的是平移 各种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的版本的话 可以评论我发给你 我就不帖在这了