css实现PK动效

文章详细介绍了如何使用CSS动画和过渡效果实现PK动效,包括人物碰撞、图标放大、头像显隐、进度条拉扯等效果,并通过React组件展示了具体的实现代码,主要依赖translateX()和宽度变化来创建各种动效。

PK动效

效果预览

在这里插入图片描述

实现原理

最初的碰触效果

一开始的人物是设置在屏幕可视范围之外的,写两个动效,最后的位置处于可视范围之内就行了,看css代码的rleftstrike,rightstrike

vs图标瞬间放大效果

看css代码的vsAnimation

头像动效

效果:最初的时候显隐,在被打败后,头像横向飞出
显隐原理:用 transition: opacity 2s 设置过渡效果,在人物碰撞动画结束之后,设置opacity 为1
横向飞出:看css代码的rightheadLose和leftheadLose

进度条以及人物推拉动效

原理都是通过宽度变化过渡去设置,我用setInterval去生成循环设置随机宽度,知道拉扯到指定次数,清除定时器。
怎样设置随机宽度呢?通过随机生成一个不大于对手分数的数,然后求自己的分数和随机生成的分数的百分比,得到的百分比去控制宽度
const temprivalScore = Math.round(Math.random()*rivalScore) //随机分数
const total = temprivalScore+myScore
setleftWidth((myScore/total)*100) //设置百分比
setrightWidth((temprivalScore/total)*100) //设置百分比

输的一方飞出的动效

看css代码的leftLose和rightLost

赢的一方放大往前顶的动效

看css代码的leftWin和rightWin

总结

总的来说呢,实现的重点就是都是通过translateX()去左右移动
另外就是用过渡属性过渡宽度的变化,通过改变宽度来达成最终效果

实现代码

import React, { FC, useEffect, useRef, useState } from "react";
import styles from "./index.less";


const text = {
  105:{
    title:"驾驶时间",
    text:"驾驶时间较多",
    bottomtext:"我的驾驶时长",
    unit:"小时",
    dataName:"winDriveTime"
  },
  106:{
    title:"打卡城市",
    text:"打卡城市较多",
    bottomtext:"我的打卡城市数",
    unit:"个",
    dataName:"cityCn"
  },
  107:{
    title:"驾驶行为",
    text:"三急行为次数较低",
    bottomtext:"我的三急行为次数",
    unit:"次",
    dataName:"badDriveCnt"
  },
  119:{
    title:"消费额度",
    text:"CPSP实付金额总数较多",
    bottomtext:"我的CPSP实付金额总数",
    unit:"元",
    dataName:"cpspPay"
  },
  111:{
    title:"远控使用",
    text:"远控功能使用次数总数较多",
    bottomtext:"我的远控功能使用次数",
    unit:"次",
    dataName:"remoteControlCnt"
  },
  110:{
    title:"APP使用",
    text:"APP打开总次数较多",
    bottomtext:"我的APP打开总次数",
    unit:"次",
    dataName:"appOpenTimes"
  },
  118:{
    title:"加油优惠",
    text:"智慧加油省钱额度总数较多",
    bottomtext:"我的智慧加油省钱额度总数",
    unit:"元",
    dataName:"gassDisAmt"
  },
  104:{
    title:"假期里程",
    text:"行驶里程数较多",
    bottomtext:"我的行驶里程",
    unit:"km",
    dataName:"driveMiles"
  },
  112:{
    title:"听歌天数",
    // text:`打开${carType==="ccs2plus"?"酷狗":"酷我"}天数较多`,
    bottomtext:"我的听歌天数",
    unit:"天",
    // dataName:`${carType==="ccs2plus"?"kwgUseDays":"kuwUseDays"}`
  },
  113:{
    title:"刷剧天数",
    text:"打开爱奇艺天数较多",
    bottomtext:"我的刷剧天数",
    unit:"天",
    dataName:"aqyUseDays"
  },
}

const PkingModal: FC = () => {
  // const pkingModalVisible = useSelector((state: any) => state.nationalday2022.pkingModalVisible);
  const pkResults = {
      "pkResult": 1,
      "rivalScore": 20,
      "myScore": 10,
      "rivalName":"对手名称",
  }
  const pkResultsRef = useRef<any>()
  pkResultsRef.current = pkResults

  const [opacity,setopacity] = useState(0)
  const [leftWidth,setleftWidth] = useState(50)
  const [rightWidth,setrightWidth] = useState(50)

  const [vsopacity,setvsopacity] = useState(1)
  const [rivalScore,setrivalScore] =useState(0)

  const leftRef = useRef<any>()

  const leftpeopleRef = useRef<any>()
  const leftheadportraitRef = useRef<any>()
  const rightpeopleRef = useRef<any>()
  const rightheadportraitRef = useRef<any>()
  const pkmodalType = 104

  const pkmodalTypeRef = useRef<any>()
  pkmodalTypeRef.current = pkmodalType

  const isBegin = useRef<any>(false)


  useEffect(()=>{
    document.documentElement.style.fontSize=(document.documentElement.clientWidth/750)*100+'px';
    if(leftRef.current){
      leftRef.current.addEventListener('animationend',()=>{
        // console.log("动画结束")
        if( isBegin.current ) return
        isBegin.current = true
        setopacity(1)
        // 执行宽度变化的函数
        setTimeout(()=>{
            const {myScore,rivalScore} = pkResultsRef.current
            // 随机生成对手的分数用来改变宽度
            let time = 0
            let timemax= 6
            if(myScore==0||rivalScore==0) timemax=2

            let temprivalScore = rivalScore;

   
            const timer = setInterval(()=>{
                  time+=1
                  // 5次的时候清除定时器 设置最终的对手分数
                  temprivalScore = Math.round(Math.random()*rivalScore)
                 

                  if(time===timemax){
                    temprivalScore = rivalScore
                    setTimeout(()=>{
                        setvsopacity(0)
                        const leftPeople = leftpeopleRef.current
                        const rightPeople = rightpeopleRef.current
                        const leftheadportrait = leftheadportraitRef.current
                        const rightheadportrait = rightheadportraitRef.current
                         // 三急行为 小的获胜
                        if( pkmodalTypeRef.current ==107){
                          if(myScore<temprivalScore){
                            // 左边胜利
                             // 右边胜利
                             leftPeople.style["animation"] = `${styles.leftWin} 1s forwards`
                             rightPeople.style["animation"] = `${styles.rightLose} 1s forwards`
                             rightheadportrait.style["animation"] = `${styles.rightheadLose} 1s forwards`
                        }else if(myScore>temprivalScore){
                             // 右边胜利
                             rightPeople.style["animation"] = `${styles.rightWin} 1s forwards`
                             leftPeople.style["animation"] = `${styles.leftLose} 1s forwards`
                             leftheadportrait.style["animation"] = `${styles.leftheadLose} 1s forwards`
                        }else{
                          // 平局
                          // 不做处理
                        }
                      }else{
                        // 大的获胜
                        if(myScore>temprivalScore){
                          // 左边胜利
                           // 右边胜利
                           leftPeople.style["animation"] = `${styles.leftWin} 1s forwards`
                           rightPeople.style["animation"] = `${styles.rightLose} 1s forwards`
                           rightheadportrait.style["animation"] = `${styles.rightheadLose} 1s forwards`
                          }else if(myScore<temprivalScore){
                              // 右边胜利
                              rightPeople.style["animation"] = `${styles.rightWin} 1s forwards`
                              leftPeople.style["animation"] = `${styles.leftLose} 1s forwards`
                              leftheadportrait.style["animation"] = `${styles.leftheadLose} 1s forwards`
                          }else{
                            // 平局
                            // 不做处理
                          }
                      } 
                      
                      

                        clearInterval(timer)
                        
                    },1000)

                    // 打开结果弹窗
                    setTimeout(()=>{
                        // dispatch({
                        //   type:"nationalday2022/save",
                        //   payload:{pkresultVisible:true,pkingModalVisible:false}
                        // })
                    },2000)
                   
                    clearInterval(timer)
                  }
                  const total = temprivalScore+myScore
                  if(total===0){
                    setleftWidth(50)
                    setrightWidth(50)
                    setrivalScore(temprivalScore)
                    clearInterval(timer)
                    setvsopacity(0)
                    setTimeout(()=>{
                      // dispatch({
                      //   type:"nationalday2022/save",
                      //   payload:{pkresultVisible:true,pkingModalVisible:false}
                      // })
                    },1000)
                   
                  }else{
                    if( pkmodalTypeRef.current ==107){
                      // 互换一下占比
                        setrightWidth((myScore/total)*100)
                        setleftWidth((temprivalScore/total)*100)
                        setrivalScore(temprivalScore)
                      }else{
                        setleftWidth((myScore/total)*100)
                        setrightWidth((temprivalScore/total)*100)
                        setrivalScore(temprivalScore)
                      }
                   
                  }
                 
            },500)
        },500)
      })
    }else{
      isBegin.current==false
    }
  },[])

  return (
    <>
      <div className={styles.pking}>
        <div
          className={styles.content}
        >
          <div className={styles.left_div} ref={leftRef}  style={{width:`${leftWidth}%`}}>
            <i className={styles.girl_icon} ref={leftpeopleRef} />
            <div className={styles.light}/>
          </div>
         
          <div className={styles.right_div}  style={{width:`${rightWidth}%`}}>
            <i className={styles.boy_icon} ref={rightpeopleRef}/>
            <div className={styles.light}/>
            <i className={styles.vs_icon} style={{opacity:vsopacity}}/>
          </div>
         
          <div className={styles.user_box} style={{opacity:opacity}}>
            <div ref={leftheadportraitRef}>
              <i className={styles.user_icon} />
              <span></span>
            </div>
            <div ref={rightheadportraitRef}>
              <i className={styles.user_icon} />
              <span>{pkResults?.rivalName}</span>
            </div>
          </div>

           <div className={styles.pkProgress} style={{opacity:opacity}}>
              <span className={styles.left_toast}>
                <i className={styles.arrow_down} />
                {pkResults?.myScore}{text[pkmodalType]?.unit}
              </span>
              <span className={styles.right_toast}>
                <i className={styles.arrow_up} />
                {rivalScore}{text[pkmodalType]?.unit}
              </span>

                <div className={styles.inner}>
                  <div className={styles.yellow}/>
                   <div className={styles.leftProgress} 
                   style={{width:`${leftWidth}%`}}
                   > </div>
                  <div className={styles.rightProgress}
                    style={{width:`${rightWidth}%`}}
                  > </div>
                </div>
              </div>
        </div>
      </div>
      {/* <Pkresult/> */}
    </>
  );
};
export default PkingModal;

// 动画
@keyframes vsAnimation {


  50%{
    transform: scale(1) skew(15deg);
  }
  80%{
    transform: scale(1.2) skew(15deg);
  }
  100% {
    transform: scale(1) skew(15deg);
  }
}

@keyframes leftWin {
  90% {
    transform: scale(1.15) skew(15deg) translateX(-20%);
  }
  100% {
    transform: scale(1.1) skew(15deg) translateX(0%);
  }
}

@keyframes rightLose {
  85% {
    transform: translateX(-5%) skew(15deg);
  }
  100% {
    transform: translateX(100%)  skew(15deg);
  }
}

@keyframes leftLose {
  85% {
    transform: translateX(5%) skew(15deg);
  }
  100% {
    transform: translateX(-100%)  skew(15deg);
  }
}

@keyframes leftheadLose {
  85% {
    transform: translateX(5%)
  }
  100% {
    transform: translateX(-130%);
  }
}
@keyframes rightheadLose {
  85% {
    transform: translateX(-5%)
  }
  100% {
    transform: translateX(130%);
  }
}

@keyframes rightWin {
  90% {
    transform: scale(1.15) skew(15deg) translateX(-20%);
  }
  100% {
    transform: scale(1.1) skew(15deg) translateX(0%);
  }
}

// 相撞的动画
@keyframes leftstrike{
    100%{
      left: 0.8rem;
    }
}

@keyframes rightstrike{
    100%{
      right: 2.2rem;
    }
}


.pking {

  height: 100vh;
  width: 100vw;
  overflow: hidden;
  .content {
    height: 100%;
    width: 130%;
    position: relative;
    .left_div {
      width: 50%;
      height: 40%;
      background: linear-gradient(to right, #f26c7312, #F26C73);
      position: absolute;
      // left: -0.7rem;
      left: -6rem;
      top: 15%;
      transform: skew(-15deg);
      animation: leftstrike 1s  forwards 0.5;
      transition: width 2s;
      .light{
        position: absolute;
        right: 0rem;
        top: -1.5rem;
        background: linear-gradient(rgba(255, 255, 255, 0.2), #fff, rgba(255, 255, 255, 0.2));
        width: 0.1rem;
        height: 9.4rem;
        border-radius: 40%;
        box-shadow: 0 0 0.2rem #fff;
      }
      .girl_icon {
        width: 5.4rem;
        height: 8.76rem;
        display: inline-block;
        background: transparent url("../../img/pkingModal/girl_icon.png")
          no-repeat;
        background-size: contain;
        position: absolute;
        right: -0.3rem;
        top: -1.5rem;
        transform: skew(15deg);
      }
    }
    .right_div {
      width: 50%;
      height: 40%;
      background: linear-gradient(to right, #38C2FC, #38c1fc38);
      display: inline-block;
      transform: skew(-15deg);
      position: absolute;
      right: -4rem;
      bottom: 36%;
      animation: rightstrike 1s  forwards 0.5;
      transition: width 2s;
    
      .light{
        position: absolute;
        left: 0rem;
        top: -2.4rem;
        background: linear-gradient(rgba(255, 255, 255, 0.2), #fff, rgba(255, 255, 255, 0.2));
        width: 0.1rem;
        height: 9.4rem;
        border-radius: 40%;
        box-shadow: 0 0 0.2rem #fff;
      }

      .boy_icon {
        width: 5.3rem;
        height: 9rem;
        display: inline-block;
        background: transparent url("../../img/pkingModal/boy_icon.png")
          no-repeat;
        background-size: contain;
        position: absolute;
        left: 0.8rem;
        top: -2.4rem;
        transform: skew(15deg);
      }
    }
    .vs_icon {
      width: 3.3rem;
      height: 4.3rem;
      background: transparent url("../../img/pkingModal/vs-icon.png")
        no-repeat;
      background-size: cover;
      display: inline-block;
      position: absolute;
      top: 5%;
      left: -1.2rem;
      animation: vsAnimation 1s;
      transform: skew(15deg);
      transition: opacity 1s;
    }
    .user_box {
      width: 80%;
      display: flex;
      justify-content: space-around;
      top: 45%;
      position: absolute;
      transition: opacity 2s;
      opacity: 0;
      > div {
        display: flex;
        flex-direction: column;
        > span {
          font-size: 0.3rem;
          font-weight: 600;
          color: black;
          margin-top: 0.2rem;
          text-align: center;
        }
      }
      > div:first-child {
        .user_icon {
          background: transparent
            url("../../img/pkingModal/right_user.png") no-repeat;
          background-size: cover;
        }
        animation: leftUserAnimation 0.4s cubic-bezier(0.95, 0.65, 0.35, 0.05);
        -webkit-animation: leftUserAnimation 0.4s
          cubic-bezier(0.95, 0.65, 0.35, 0.05);
      }
      > div:last-child {
        .user_icon {
          background: transparent
            url("../../img/pkingModal/left_user.png") no-repeat;
          background-size: cover;
        }
        animation: righttUserAnimation 0.4s cubic-bezier(0.95, 0.65, 0.35, 0.05);
        -webkit-animation: righttUserAnimation 0.4s
          cubic-bezier(0.95, 0.65, 0.35, 0.05);
      }
      .user_icon {
        width: 2.31rem;
        height: 2.31rem;
        display: inline-block;
      }
    }
    .progress_bar {
      bottom: 17%;
      position: absolute;
      width: 100vw;
      .left_pg {
        width: 3rem;
        height: 0.6rem;
        background: transparent
          url("../../img/pkingModal/left_progress.png") no-repeat;
        background-size: cover;
        display: inline-block;
        position: relative;
        z-index: 1;
        .left_toast {
          background-color: #fe5442;
          padding: 0.1rem 0.2rem;
          border-radius: 0.1rem;
          display: inline-block;
          position: relative;
          top: -1rem;
          left: -0.3rem;
          color: #fff;
          font-size: 0.28rem;
        }
        .arrow_down {
          width: 0.15rem;
          height: 0.15rem;
          display: inline-block;
          background-color: #fe5442;
          transform: rotate(45deg);
          position: absolute;
          bottom: -0.05rem;
        }
      }
      .middle {
        background-color: #fdffc6;
        width: 0.5rem;
        height: 0.48rem;
        display: inline-block;
        position: absolute;
        left: 3.5rem;
        top: 0.02rem;
      }
      .right_pg {
        width: 3rem;
        height: 0.6rem;
        background: transparent
          url("../../img/pkingModal/right_progress.png") no-repeat;
        background-size: cover;
        display: inline-block;
        position: relative;
        z-index: 1;
        left: -0.15rem;
        .right_toast {
          background-color: #fa8236;
          padding: 0.1rem 0.2rem;
          border-radius: 0.1rem;
          display: inline-block;
          position: relative;
          left: 0;
          top: 0.9rem;
          color: #fff;
          font-size: 0.28rem;
        }
        .arrow_up {
          width: 0.15rem;
          height: 0.15rem;
          display: inline-block;
          background-color: #fa8236;
          transform: rotate(45deg);
          position: absolute;
          top: -0.05rem;
          right: 0.2rem;
        }
      }
    }
    > p {
      position: absolute;
      bottom: 3%;
      width: 100vw;
      color: #fff;
      font-size: 0.3rem;
    }

    .pkProgress {
      transition: opacity 2s;
      opacity: 0;
      position: absolute;
      // top: 9.5rem;
      bottom: 18%;
      width: 70%;
      height: 0.68rem;
      background: #fee1d0aa;
      // margin: auto;
      margin-top: 0.8rem;
      border-radius: 0.5rem;
      display: flex;
      padding: 0.04rem 0rem;
      left:0.35rem;
      .left_toast {
        min-width: 1.9rem;
        background-color: #fe5442;
        padding: 0.1rem 0.2rem;
        border-radius: 0.1rem;
        display: inline-block;
        position: absolute;
        top: -1rem;
        left: 0rem;
        color: #fff;
        font-size: 0.28rem;
        .arrow_down {
          width: 0.15rem;
          height: 0.15rem;
          display: inline-block;
          background-color: #fe5442;
          transform: rotate(45deg);
          position: absolute;
          bottom: -0.05rem;
        }
      }

      .right_toast {
        background-color: #fa8236;
        min-width: 1.9rem;
        padding: 0.1rem 0.2rem;
        border-radius: 0.1rem;
        display: inline-block;
        position: absolute;
        right: 0;
        top: 0.9rem;
        color: #fff;
        font-size: 0.28rem;
        .arrow_up {
          width: 0.15rem;
          height: 0.15rem;
          display: inline-block;
          background-color: #fa8236;
          transform: rotate(45deg);
          position: absolute;
          top: -0.05rem;
          right: 0.2rem;
        }
      }

      .inner {
        position: relative;
        border-radius: 0.4rem;
        height: 100%;
        width: 100%;
        margin: auto;
      }
      .yellow{
        position: absolute;
        background-color: #FFFFDC;
        width: 95%;
        height: 95%;
        border-radius: 0.4rem;
        left: 0.2rem;
        top: 0.02rem;
      }

      .leftProgress {
        position: absolute;
        top: 0;
        left: 0.06rem;
        height: 98%;
        width: 0;
        transition: width 1s;
        overflow: hidden;
        border-radius: 0.4rem 0 0 0.4rem;
        &::after{
          content: '';
          width: 100%;
          height: 100%;
          background: linear-gradient(#FFB6AE,#FE5442 20%);
          display: block;
          transform: skewX(-10deg);
          position: absolute;
          top: 0;
          left: -0.1rem;
        }
      }

      .rightProgress {
        position: absolute;
        top: 0;
        right: 0.06rem;
        width: 0;
        height: 98%;
        transition: width 1s;
        overflow: hidden;
        border-radius: 0 0.4rem  0.4rem 0;
        &::after{
          content: '';
          width: 100%;
          height: 100%;
          background: linear-gradient(#FFBC8C,#FF9344 20%);
          display: block;
          transform: skewX(-10deg);
          position: absolute;
          top: 0;
          right: -0.1rem;
        }
      }
    }
  }

}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值