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