react中使用styled-components制作动画

一 简单介绍

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;

猜你喜欢

转载自blog.csdn.net/LittleMoon_lyy/article/details/122901590