記錄--進度條真的是勻速的,不信你看

林恒發表於2024-07-22

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

引言

眾所周知,進度條是程式設計師大大模擬的程式執行進度,一般會在某些數值卡住不動,引起99%懸案。但是背後的原理你真的清楚嗎,其實進度條真的是勻速運動的!

先來看看效果

接下來開始實現

建立一個矩形,然後摺疊起來,完成!

建立一個容器,用於寬度限制

摺疊形狀實際大小肯定是大於需求大小的,在這得用絕對定位限制,防止影響到頁面佈局。將每一段用block展示,後續使用3d變換摺疊起來。

// stylus
.outsidebox
    position relative
    height 20px
    .loading-bar 
        display flex
        position absolute
        transform-style preserve-3d
        height 20px
        .block
            height 100%
            background #000f2e
            
<div class="outsidebox" :style="{width: `${showWidth}px`}">
    <div class="loading-bar">
        <div class="block"></div>
    </div>
</div>

建立主函式,接收引數為最終可視寬度

因摺疊塊起始是水平塊,所以限制塊個數為奇數,以橫豎橫方式生成。 藝術就是派大星! 隨機數!塊的寬度採用隨機比值的方式生成,透過橫向的比值和逆推實際總寬度。

const createRandomRatio = (num: number) => {
    // 建立隨機比值,合為1
    let sum = 0;
    const numbers = [];
    for (let i = 0; i < num; i++) {
        let randomNumber = Math.random(); 
        numbers.push(randomNumber);
        sum += randomNumber;
    }
    return numbers.map(num => num / sum);
}
const calcTotalWidht = (ratio: number[], width: number) => {
    // 根據使用者輸入寬度,反向求出摺疊前寬度
    let r = 0;
    for(let i = 0; i < ratio.length; i+=2) {
        r += ratio[i];
    }
    let w = width / r;
    return w
}
const createRadomRect = (width: number) => {
    // 主函式入口,建立摺疊矩形塊
    let num = 11;
    let widthRatio = createRandomRatio(num); // 建立隨機比值
    let calcW = calcTotalWidht(widthRatio, width); // 逆向推導實際寬度
    showWidth.value = width; // 使用者看得到的寬度
    totalWidth.value = calcW; // 實際的寬度
}

實現建立橫塊與縱塊

上一段獲取到總寬度,接下來根據比值生成具體的塊。

1.生成橫塊,返回橫塊的水平,垂直偏移位置

const createHorizontal = (id: number, horizontal: number, vertial: number, width: number) => {
    data.value.push({
        id,
        width,
        transform: `translateX(${horizontal}px) translateZ(${vertial}px)`
    })
    return {
        transX: horizontal,
        transZ: vertial
    }
}
  1. 接收橫塊位置,生成縱塊位置,返回縱塊水平位置及垂直位置
const createVertical = (id: number, horizontal: number, vertial: number, width: number) => {
    let direct = prevDirect.value == 1 ? -1 : 1; // 逆時針旋轉則x右移z上移,順時針則x右移z下移
    let transX = prevDirect.value * vertial + width / 2; // 如前一個旋轉塊方向相反則需更新垂直移動方向,前逆後順更改為左移
    let transZ = horizontal * direct + width / 2 * -direct
    data.value.push({
        id,
        width,
        transform: `rotateY(${90 * direct}deg) translateZ(${transZ}px) translateX(${transX}px)`
    })
    prevDirect.value = direct;
    return {
        transX: horizontal + width * -1,
        transZ: vertial + width * -direct
    }
}
  1. 調整主函式,增加迴圈生成橫縱塊邏輯
const totalWidth = ref(0);
const showWidth = ref(0)
const data = ref([] as any)
const prevDirect = ref(1);
const createRadomRect = (width: number) => {
    // 主函式入口,建立摺疊矩形塊
    let num = 11;
    let widthRatio = createRandomRatio(num); // 建立隨機比值
    let calcW = calcTotalWidht(widthRatio, width); // 逆向推導實際寬度
    showWidth.value = width; // 使用者看得到的寬度
    totalWidth.value = calcW; // 實際的寬度
    let blockWidth = 0;
    let transX = 0;
    let transZ = 0;
    for(let i = 0; i < num; i++) {
        let rectWidth = Math.floor(calcW * widthRatio[i]);
        if(i == num - 1) {
            // 最後一個橫塊,修正floor帶來的寬度缺失
            rectWidth = width - blockWidth
        } 
        if(i % 2 == 0) {
            blockWidth += rectWidth;
            let obj = createHorizontal(i, transX, transZ, rectWidth)
            transX = obj.transX;
            transZ = obj.transZ;
        } else {
            let obj = createVertical(i,transX, transZ, rectWidth)
            transX = obj.transX;
            transZ = obj.transZ;
        }
    }
}

初具規模了,嘿嘿

實現進度動畫

將進度抽象為0-1,對應的UI展示效果就是背景色的填充進度。因為是分塊,所以將總體的寬度*進度,再分攤到每個塊上進行顯示。動畫採用requestAnimationFrame API實現,懂得都懂。每步長度設定為0.001,這樣看起來比較美觀。

const progress = ref(0)
const calcChangeRect = (progress: number) => {
    // 計算每個矩形的進度
    let current = progress * totalWidth.value;
    let list = data.value;
    let add = 0;
    for(let i = 0; i < list.length; i++) {
        if(list[i].width + add > current) {
            list[i].progress = (current - add) / list[i].width;
            break
        } else {
            list[i].progress = 1;
            add += list[i].width;
        }
    }
}
const createAnimation = () => {
    let animationId = 0;
    let start = () => {
        progress.value += 0.001;
        if(progress.value < 1) {
            animationId = window.requestAnimationFrame(start)
        } else {
            progress.value = 1;
            window.cancelAnimationFrame(animationId);
        }
        calcChangeRect(progress.value)
    }
    animationId = window.requestAnimationFrame(start)
}

最後加點debugger工具

1.設定滑鼠旋轉事件

const rotateStyle = ref("")
const useMouseMove = ref(false)
const toggleMouseMove = () => {
    useMouseMove.value = !useMouseMove.value
}
const handleMouseMove =  (event: MouseEvent) => {
    if(!useMouseMove.value) return 0
    let pageX = event.pageX,
        pageY = event.pageY;
    const winW = window.innerWidth / 2,
          winH = window.innerHeight / 2;
    let X = 0;
    let Y = 0;
    if(pageX < winW) {
        X = -((winW - pageX) / winW * 90);
    } else {
        X = (pageX - winW) / winW * 90
    }

    if(pageY < winH) {
        Y = -((winH - pageY) / winH * 90);
    } else {
        Y = (pageY - winH) / winH * 90
    }
    rotateStyle.value = `transform: rotateY(${X}deg) rotateX(${Y}deg)`
}
document.addEventListener("mousemove", handleMouseMove);

2.設定主檢視和45度檢視

const setDisplay45 = () => {
    useMouseMove.value = false;
    rotateStyle.value = `transform: rotate3d(1, 1, 0, 45deg)`
}
const setDisplay0 = () => {
    useMouseMove.value = false;
    rotateStyle.value = ``
}

結語

這個專案是為了熟悉3d變換,在使用translateZ、translateX想了很久,腦子不夠用了。還有很多地方可以調整為配置項,設定塊數,塊顏色等,甚至封裝成api,下次一定。

本文轉載於:https://juejin.cn/post/7370682158103347238

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

記錄--進度條真的是勻速的,不信你看

相關文章