一 简单介绍
1. 一个简单的效果
页面数字从0开始每隔一段时间+1,到10后数字有一个放大缩小的动画,如下:
2.如果不使用styled-components,我们可以这么做:
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss'
const Test2 = () => {
const [count, setCount] = useState(0)
const numRef = useRef()
useEffect(() => {
const timer=setInterval(()=>{
if(count<10){
setCount(count=>count+1)
}else{
numRef.current.classList.add(styles.expand)
clearInterval(timer)
}
},90)
return () => {
clearInterval(timer)
}
},[count]);
return (
<>
<div ref={numRef} className={styles.num}>
{count}
</div>
</>
);
}
export default Test2;
index.module.scss:
.expand{
animation: expandAnim 2s 0.5s linear 2;
}
.num{
display: inline-block;
padding: 2rem 1rem;
font-size: 1.2rem;
}
@keyframes expandAnim{
from {
transform: scale(2);
}
to {
transform: rscale(1);
}
}
3.如果使用styled-components的话,代码如下:
import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import styles from './index.module.scss'
const Expand = styled.div`
display: inline-block;
padding: 2rem 1rem;
font-size: 1.2rem;
&.expandCount{
animation: expandAnim 2s 0.5s linear 2;
}
@keyframes expandAnim{
from {
transform: scale(2);
}
to {
transform: rscale(1);
}
}
`;
const Test1 = () => {
const [count, setCount] = useState(0)
const numRef = useRef()
useEffect(() => {
const timer=setInterval(()=>{
if(count<10){
setCount(count=>count+1)
}else{
numRef.current.classList.add('expandCount')
clearInterval(timer)
}
},90)
return () => {
clearInterval(timer)
}
},[count]);
return (
<>
<Expand ref={numRef}>
{count}
</Expand>
</>
);
}
export default Test1;
注意,这里的&.expandCount{···}
实际上是在父元素上定义了一个class, 如果想在jsx里使用的话,可以这样:
<Expand ref={numRef} className='expandCount'>
{count}
</Expand>
这样就实现了在父元素里定义了样式,然后在需要的时候引入样式使样式生效。
3.问题
那既然不用styled-components也能实现,那为什么还要使用这个库呢?
关键是我们可以通过styled-components,在css中使用变量。
接下来看大转盘的例子。
二 大转盘
我们以大转盘为例,不同的奖品对应不同的旋转角度。考虑到大转盘主要在app端,这里的div宽度设置为750px。
一般来说,转盘分为四个部分:外层的装饰圈,圆盘,圆盘里的奖品信息,指针。
第一步:先把外层的装饰以及圆盘加上去
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss'
const Test3 = () => {
return (
<>
<div className={styles.turnable_wrap}>
{/* 转盘外层的装饰层 */}
<div className={styles.turnable_decorate}>
{/* 圆盘里的部分 */}
<div className={styles.turnable_circle}>
</div>
</div>
</div>
</>
);
}
export default Test3;
index.module.scss:
.turnable_wrap{
width:750px;
height:750px;
margin: 0 auto;
}
.turnable_decorate{
background:url('./assets/decorate.png') center center no-repeat;
background-size: contain;
width:720px;
height:720px;
position:relative;
}
.turnable_circle{
width:496px;
height:496px;
// border: 3px solid #EC833E;
border: 3px solid #fff;
border-radius: 50%;
position:absolute;
left:50%;
top:50%;
margin-top:3px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
效果如下图:
这里为了突出圆盘的效果,圆盘边框先置为白色。
第二步:把圆盘分为6块
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss'
const Test3 = () => {
const prizes = [
{
name:'奖品1',
},
{
name:'奖品2',
},
{
name:'奖品3',
},
{
name:'奖品4',
},
{
name:'奖品5',
},
{
name:'奖品6',
},
]
return (
<>
<div className={styles.turnable_wrap}>
{/* 转盘外层的装饰层 */}
<div className={styles.turnable_decorate}>
{/* 圆盘里的部分 */}
<div className={styles.turnable_circle}>
{
prizes.map((item,index)=>{
return <div key={index} className={styles.turnable_bg} style={{transform:`rotate(${(-30+60*index)}deg) skewY(-30deg)`}}></div>
})
}
</div>
</div>
</div>
</>
);
}
export default Test3;
css部分:
.turnable_wrap{
width:750px;
height:750px;
margin: 0 auto;
}
.turnable_decorate{
background:url('./assets/decorate.png') center center no-repeat;
background-size: contain;
width:720px;
height:720px;
position:relative;
}
.turnable_circle{
width:496px;
height:496px;
// border: 3px solid #EC833E;
border: 3px solid #fff;
border-radius: 50%;
position:absolute;
left:50%;
top:50%;
margin-top:3px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
overflow: hidden; //把turnable_bg中多出来的隐藏
}
.turnable_bg{
width:248px;
height:248px;
position:absolute;
left: 50%;
margin: 0 auto;
transform-origin:0 100%; //重要
border:3px solid #fff;
}
先来看一下效果:
这里有几点要注意:
1.六个区域的初始位置
每个区域都统一在left:50%; top:0
的位置,然后统一设置旋转和倾斜角度
2.六个区域的旋转和倾斜过程
看图:
每个区域实际上都是以左下那个点为旋转基点,先旋转后倾斜-30度。
第一个先旋转-30度,第二个旋转30度,以此类推,便能得到
style={{transform:`rotate(${(-30+60*index)}deg) skewY(-30deg)`}}`
第三步:设置里面的奖品内容和指针
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss'
const Test3 = () => {
const prizes = [
{
name:'奖品1',
},
{
name:'奖品2',
},
{
name:'奖品3',
},
{
name:'奖品4',
},
{
name:'奖品5',
},
{
name:'奖品6',
},
]
return (
<>
<div className={styles.turnable_wrap}>
{/* 转盘外层的装饰层 */}
<div className={styles.turnable_decorate}>
{/* 圆盘里的部分 */}
<div className={styles.turnable_circle}>
{
prizes.map((item,index)=>{
return (
<div key={index}>
<div className={styles.turnable_bg} style={{transform:`rotate(${(-30+60*index)}deg) skewY(-30deg)`}}>
</div>
<div className={styles.turnable_prize} style={{transform:`rotate(${-60+(index+1)*60}deg) translateX(-124px)`}}>
<p>{item.name}</p>
<img src={require('./assets/car.png')} alt=''/>
</div>
</div>
)
})
}
{/* 指针 */}
<div className={styles.turnable_pointer}>
<img src={require('./assets/pointer.png')} alt=''/>
</div>
</div>
</div>
</div>
</>
);
}
export default Test3;
css部分:
.turnable_wrap{
width:750px;
height:750px;
margin: 0 auto;
}
.turnable_decorate{
background:url('./assets/decorate.png') center center no-repeat;
background-size: contain;
width:720px;
height:720px;
position:relative;
}
.turnable_circle{
width:496px;
height:496px;
// border: 3px solid #EC833E;
border: 3px solid #fff;
border-radius: 50%;
position:absolute;
left:50%;
top:50%;
margin-top:3px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
overflow: hidden; //把turnable_bg中多出来的隐藏
}
.turnable_bg{
width:248px;
height:248px;
position:absolute;
left: 50%;
margin: 0 auto;
transform-origin:0 100%; //重要
border:3px solid #fff;
}
.turnable_prize{
width:248px;
height:248px;
position:absolute;
left: 50%;
margin: 0 auto;
transform-origin: left bottom;
text-align: center;
}
.turnable_pointer{
position:absolute;
left: 50%;
top:50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
width:238px;
height:238px;
}
效果:
这里要注意每个奖品的旋转,看图:
还是以左下角的那个点为旋转基点,先旋转后沿着x轴平移半个边长。
第一个不旋转,第二个旋转60度,第三个旋转120度,以此类推,从而得到
style={{transform:`rotate(${-60+(index+1)*60}deg) translateX(-124px)`}}
接下来就是指针,这里用一张图,固定在中心位置即可。
代码一并放在了上面,看一下最终效果:
第四步:控制大转盘的转动角度
先看一下效果:
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss'
import styled, { keyframes } from 'styled-components'
// 这里的keyframes不能拿到props,失败的尝试
// const rotateProcess = keyframes`
// 0%{
// transform: rotate(0deg);
// }
// 100%{
// transform: rotate(${props=>(props.num*60+1800)+'deg'});
// }
// `;
const Rotate = styled.div`
width:496px;
height:496px;
@keyframes rotateWheel {
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(${props=>(1800-props.num*60)+'deg'});
}
}
&.rotateAnim{
animation: rotateWheel 3s ease-out;
}
&.afterRotate {
transform: rotate(${props=>-props.num*60+'deg'});
}
`;
const Test3 = () => {
const prizes = [
{
name:'奖品1',
},
{
name:'奖品2',
},
{
name:'奖品3',
},
{
name:'奖品4',
},
{
name:'奖品5',
},
{
name:'奖品6',
},
]
const [prizeNum, setPrizeNum] = useState(3)
const [isClicked, setIsClicked] = useState(false)
const rotatePart = useRef()
const rotateTurnable = () => {
const randomNum = parseInt(Math.random() * 6)
console.log('randomNum: '+ randomNum);
// rotatePart.current.classList.add('rotateAnim')
setPrizeNum(randomNum)
setIsClicked(true)
setTimeout(()=>{
// rotatePart.current.classList.remove('rotateAnim')
// rotatePart.current.classList.add('afterRotate')
setIsClicked(false)
},3000)
}
return (
<>
<div className={styles.turnable_wrap}>
{/* 转盘外层的装饰层 */}
<div className={styles.turnable_decorate}>
{/* 圆盘里的部分 */}
<div className={styles.turnable_circle}>
<Rotate num={prizeNum} ref={rotatePart} className={isClicked? 'rotateAnim': 'afterRotate'}>
{
prizes.map((item,index)=>{
return (
<div key={index}>
<div className={styles.turnable_bg} style={{transform:`rotate(${(-30+60*index)}deg) skewY(-30deg)`}}>
</div>
<div className={styles.turnable_prize} style={{transform:`rotate(${-60+(index+1)*60}deg) translateX(-124px)`}}>
<p>{item.name}</p>
<img src={require('./assets/car.png')} alt=''/>
</div>
</div>
)
})
}
</Rotate>
</div>
{/* 指针 */}
<div className={styles.turnable_pointer} onClick={rotateTurnable}>
<img src={require('./assets/pointer.png')} alt=''/>
</div>
</div>
</div>
</>
);
}
export default Test3;
这里有几点需要注意:
1.转动的区域我用<Rotate></Rotate>
标签包裹了,对应的css要设置width和height,以及旋转中心点为center center
2.原本打算用useRef的current.classList手动添加删除样式的,但是这里有个问题:
useRef的current里增减样式,整个页面并不会刷新,必须得结合useEffect和useState才可以,但是这么做比较麻烦。于是我新增了一个变量isClicked专门用于控制样式的变化,这么做了之后这里的整个ref都可以不要了,为了记录问题保留了这段代码。
3.
<Rotate num={prizeNum} ref={rotatePart} className={isClicked? 'rotateAnim': 'afterRotate'}>
中,用prizeNum指定每次中奖的奖品,className用来指定样式,这里有个点需要注意:
一开始我将className设置为className={isClicked? 'rotateAnim': ''}
,旋转之后打算通过rotatePart.current.classList.add('afterRotate')
手动指定最后大转盘的样式,但是实际上isClicked为false之后,className为’’,即使手动指定了样式,最后也会被清空,这里一定要注意。
4.styled-components中提供了keyframes,rotateProcess中就是直接用了keyframes来指定动画,但是有个问题就是里面不能加参数。
最终代码:
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss'
import styled from 'styled-components'
const Rotate = styled.div`
width:496px;
height:496px;
@keyframes rotateWheel {
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(${props=>(1800-props.num*60)+'deg'});
}
}
&.rotateAnim{
animation: rotateWheel 3s ease-out;
}
&.afterRotate {
transform: rotate(${props=>-props.num*60+'deg'});
}
`;
const Test3 = () => {
const prizes = [
{
name:'奖品1',
},
{
name:'奖品2',
},
{
name:'奖品3',
},
{
name:'奖品4',
},
{
name:'奖品5',
},
{
name:'奖品6',
},
]
const [prizeNum, setPrizeNum] = useState(0)
const [isClicked, setIsClicked] = useState(false)
const rotateTurnable = () => {
const randomNum = parseInt(Math.random() * 6)
console.log('randomNum: '+ randomNum);
setPrizeNum(randomNum)
setIsClicked(true)
setTimeout(()=>{
setIsClicked(false)
},3000)
}
return (
<>
<div className={styles.turnable_wrap}>
{/* 转盘外层的装饰层 */}
<div className={styles.turnable_decorate}>
{/* 圆盘里的部分 */}
<div className={styles.turnable_circle}>
<Rotate num={prizeNum} className={isClicked? 'rotateAnim': 'afterRotate'}>
{
prizes.map((item,index)=>{
return (
<div key={index}>
<div className={styles.turnable_bg} style={{transform:`rotate(${(-30+60*index)}deg) skewY(-30deg)`}}>
</div>
<div className={styles.turnable_prize} style={{transform:`rotate(${-60+(index+1)*60}deg) translateX(-124px)`}}>
<p>{item.name}</p>
<img src={require('./assets/car.png')} alt=''/>
</div>
</div>
)
})
}
</Rotate>
</div>
{/* 指针 */}
<div className={styles.turnable_pointer} onClick={rotateTurnable}>
<img src={require('./assets/pointer.png')} alt=''/>
</div>
</div>
</div>
</>
);
}
export default Test3;