文章目录

  • css实现可控进度条动效
  • 实现效果
  • 实现思路
  • 实现代码


css实现可控进度条动效

实现效果

swift 进度条渐变色绘制 css 进度条动画_进度条

实现思路

整体思路就是通过监听宽度的过渡效果,因为需要一步一步去增加,进度条变化的同时控制数字和节点的变化,所以我们监听了过渡结束事件,并增加执行队列,保证了进度条动画结束后在执行下一次的动画执行

(1)增加旗帜 addFlag方法,传入一个需要增加的数量,然后需要分条件去判断剩余的位置是否足够放下增加的数量?如果够,直接调用改变进度条宽度的方法
如果不够,需要判断剩余的位置是否大于0,如果是,那么就先占满,把剩下的放进队列里,等占满后在继续执行;如果剩余位置为0,需要先改变宽度为0,然后在执行宽度的变化函数

(2)监听的回调函数中,需要判断在达到目的节点后队列是否有值,有的话,执行队列的尾值

实现重点:递归,队列、过渡结束监听

实现代码

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

const Progressbar:FC<any> = ()=>{


  //初始化最开始的旗帜数
  const initFlag = (num:number)=>{
    const myflagNum = num
    if(myflagNum==0) return
    const mychangeflagNum = (myflagNum%5)===0?(Math.floor(myflagNum/5)-1)*5:Math.floor(myflagNum/5)*5
    setchangeflagNum(mychangeflagNum);
    addFlag(myflagNum)
  }


  useEffect(()=>{
    document.documentElement.style.fontSize=(document.documentElement.clientWidth/750)*100+'px';
    barRef.current.addEventListener("transitionend",widthchangend)
    initFlag(4);
  },[])

  const [barWidth,setbarWidth] =useState<number>(0)
    // 当前到达的节点
    const [nownode,setnowNode] =useState<number>(0)

    // 当前旗帜数
    const [flagNum,setflagNum] =useState<number>(0)

    //变化的旗帜数
    const [changeflagNum,setchangeflagNum] =useState<number>(0)

    const changeflagNumRef = useRef<any>(0);
    const flagNumRef = useRef<any>(0);
    const nownodeRef = useRef<any>(0);

    changeflagNumRef.current = changeflagNum
    flagNumRef.current = flagNum
    nownodeRef.current = nownode
    
    const barRef = useRef<any>(null);

    const queueRef = useRef<any>([]);//执行队列
    const addingRef = useRef<any>(false);//监听状态

    const widthObj:any = {
      "node1":20,
      "node2":40,
      "node3":60,
      "node4":80,
      "node5":100,
  }

  // 宽度变化结束 数字改变增加 节点增加
  const widthchangend  =()=>{
      setchangeflagNum( changeflagNumRef.current+1)
      setnowNode((  nownodeRef.current+1))
       // 增加到指定的数字了
      if(changeflagNumRef.current>= flagNumRef.current ){
        addingRef.current = false
        const num = queueRef.current.pop()
        if(num&&num>0){
          addFlag(num)
        }
        return
      }
      const nowFlag = changeflagNumRef.current%5+1;
      setbarWidth(widthObj["node"+nowFlag])
  }

  /**
   * 
   * @param flagNum // 需要更新的旗帜总数
   * @returns 
   */
    const changeWidth = (flagNum:number)=>{
        addingRef.current = true
        flagNumRef.current = flagNum
        setflagNum(flagNum)
    
        if(flagNum===0) return;

        //先增加一步 在这一步结束后 继续增加到指定数字
        const nowFlag = changeflagNumRef.current%5+1;
        setbarWidth(widthObj["node"+nowFlag])
    }


    // 增加旗帜的方法
    /**
     * 
     * @param num //需要增加的旗帜数
     * @returns 
     */
    const addFlag = (num:number)=>{
      if(num<=0){
        return 
      }

      // 如果目前正在执行动画中 那么把下次添加的加到队列里执行
      if(addingRef.current){
        console.log("加入队列",num)
        queueRef.current.push(num)
        return
      }
      
      // 变化的旗帜数===获得的旗帜总数
      // 不等于的时候就说明是进度条未加载完毕
      if(changeflagNum!==flagNumRef.current){
        setchangeflagNum(flagNumRef.current);
      }
     
  
      let k = 5; //k表示进度条上剩余的位置
      if(flagNumRef.current%5==0&&flagNumRef.current!==0){
          k=0
      }
      else{
         k  = 5-(flagNumRef.current%5)
      }
      console.log("剩余的位置",k,"需要增加的数量",num)
  
      // 剩余位置够放置需要增加的数量
      if(num<=k){
        const newflagNum = flagNumRef.current+num;
        changeWidth(newflagNum)
        return 
      }
      // 剩余的位置不够放置需要增加的数量
      else{
        // 没有剩余位置了
        if(k===0){
          // 需要换一页
          setbarWidth(0)
          setnowNode(0)
            // 加延时是为了移除过渡属性之后再去改变宽度,避免宽度过渡变为0的过渡被监听到
            setTimeout(()=>{
              // 需要判断增加的num是否大于5
              if(num>=5){
                changeWidth( flagNumRef.current +5)
                // 剩下的放进执行队列里 下次添加
                queueRef.current.push(num-5)
              }else{
                changeWidth( flagNumRef.current +num)
                
              }
            },500)
          return 
        }
        // 剩余位置k 把剩余的位置k占满 再次递归的时候就会回到上面的情况
        else{
          changeWidth( flagNumRef.current+k)
          queueRef.current.push(num-k)
        }
      }
    }


  return(
    <div className={styles.progress}>
       <div className={styles.num}>
          <span className={styles.text}>我的旗帜数量:{changeflagNum>flagNum?flagNum:changeflagNum}/332<span className={styles.tipIcon} /></span>
        </div>

      {/* 底 */}
      <div className={styles.bar}>
        {/* 需要宽度变化的那层 */}
        {/* 进度条 */}
        <div className={styles.bar2} 
          ref = {barRef}
          style={{width:`${barWidth}%`,
          transition:barWidth===0?"":`width 1s linear`
        }}
          >

            {/* 蒙层 主要是为了颜色的渐变 */}
            <div className={styles.masker} />
        </div>

         {/* 节点 旗帜 数字 */}
        <div className={styles.warpper}>
          {
              [0,1,2,3,4,5].map((item)=>{
              return(<div className={styles.nodeWrapper} key={item}>
                    
                  <div style={{position:"relative", left:"-0.3rem",width:"0.7rem",display:"flex",justifyContent:"flex-end",alignItems:"flex-start"}}>
                    <span className={styles.number}>
                      {
                        flagNum%5==0&&flagNum!=0?item+(Math.floor(flagNum/5)-1)*5:
                        item+Math.floor(flagNum/5)*5
                      }
                    </span>
                    <span className={styles.flag}/>
                  </div>
                  {/* 节点得判断 */}
                  {
                    item==5?<div className={styles.nodeEnd} >
                      <span className={styles.giftIcon}
                      style={{ animation:barWidth==100?`${styles.twinkle} 0.8s infinite 1s`:""}}
                      />
                    </div>:
                    <div className={styles.node1}>
                      <span className={styles.node2} style={{
                        opacity:item%5<=nownode?1:0,
                        transition:nownode===0?"":`opacity 1.5s`
                      }}/>
                    </div>
                  }
                  
                </div>)
              })
          }
        </div>

        <div onClick={()=>{addFlag(3)}} className={styles.btn}>增加旗帜</div>
      </div>
      
    </div>
  )
}

export default Progressbar
.progress{
  position: relative;
  width: 100%;
  .num{
    background:linear-gradient(#FFF2E8,#FFE2BE);
    border-radius: 0.15rem;
    width: 90%;
    height: 1rem;
    margin: auto;
    margin-top: 0.3rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .text{
      color:#7A1C0E;
      font-size: 0.29rem;
      margin-left: 0.15rem;
    }
  }
  .bar{
    background-image: url(../../img/progress/flagProgressBar.png);
    background-repeat: no-repeat;
    background-size: 100% 100%;
    width: 6rem;
    height: 0.2rem;
    margin: 1rem auto;
    position: relative;
    .bar2{
      height: 0.2rem;
      border-radius: 0.2rem;
      background:linear-gradient(to right, #FF9445,#FFE29D); //外层的边框
      display: flex;
      align-items: center;
      transform: translateY(-0.04rem);
      position: absolute;
    
      .masker {
        position: absolute;
        width: calc(100% - 0.05rem);
        height: 0.16rem;
        opacity: 0.6;
        border-radius: 0.2rem;
        background:linear-gradient(to right, #FF8751,#FFE7D0);
        margin-left: 0.025rem;
       
      }
    }
    .bar2::after{
          width: calc(100% - 0.05rem);
          height: 0.16rem;
          border-radius: 0.2rem;

          // 制造倾斜条纹
          background: repeating-linear-gradient(-45deg, #FF8F5B  25%, #FE5404 0, #FE5404  46%, #FF8F5B  0, #FF8F5B  75%, #FE5404 0);
          background-size: 0.2rem 0.2rem;
          content: '';
          border-radius: 0.2rem;
          margin-left: 0.025rem;

          // 条纹移动的动画
          animation: panoramic 30s linear infinite;
          
    }
  }

  .warpper{
      position: absolute;
      width: 100%;
      display: flex;
      top: -0.4rem;
      left: 3%;
      justify-content: space-between;
      align-items: flex-start;
      z-index: 2;
      .num0{
        position: absolute;
        top: -0.1rem;
        left: -18%;
        font-size: 0.24rem;
        color: #E5571D;
        vertical-align: super;
      }
      .nodeWrapper{
        display: flex;
        flex-direction:column;
        align-items: flex-start;
        .number{
          font-size: 0.24rem;
          color: #E5571D;
          vertical-align: super;
          margin:-0.1rem 0.1rem 0 0;
        }
        .flag{
          display: inline-block;
          background-image: url(../../img/progress/flag.png);
          background-repeat: no-repeat;
          background-size: cover;
          width: 0.23rem;
          height: 0.33rem;
          vertical-align: text-top;
        }

        .node1{
          background:url(../../img/progress/node1.png);
          background-repeat: no-repeat;
          background-size: 100% 100%;
          display: block;
          width: 0.23rem;
          height: 0.23rem;
          position: relative;
          display: flex;
          justify-content: center;
          align-items: center;
          .node2{
            background:linear-gradient(45deg, #FE4107, #FF925E);
            border-radius: 50%;
            width: 0.15rem;
            height: 0.15rem;
            display: inline-block;
          }
        }
        .nodeEnd{
          background:url(../../img/progress/node1.png);
          background-repeat: no-repeat;
          background-size: 100% 100%;
          width: 0.23rem;
          height: 0.23rem;
          position: relative;
          z-index: 1;
          border-radius: 50%;
          transform: scale(1.65);
          .giftIcon{
            width: 0.25rem;
            height: 0.29rem;
            background-image: url(../../img/progress/giftIcon.png);
            background-size: cover;
            display: inline-block;
            position: absolute;
            top: 0;
            left: -0.015rem;
          }
          .giftIcon2{
            width: 0.25rem;
            height: 0.29rem;
            background-image: url(../../img/progress/openGift.png);
            background-size: cover;
            display: inline-block;
            position: absolute;
            top: 0;
            left: -0.015rem;
            transform: scale(1.2);
          }
        }
      }
     
  }

  .btn{
    background-color: #E5571D;
    border-radius: 0.5rem;
    width: 2rem;
    margin: auto;
    position: relative;
    top: 0.5rem;
    text-align: center;
    line-height: 0.5rem;
    height: 0.5rem;
    color: wheat;
    font-weight: bold;
    cursor: pointer;
  }
}   

/*进度条动画*/
@keyframes panoramic{
  to {
    background-position: 200% 0;
  }
}