🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
引言
眾所周知,進度條是程式設計師大大模擬的程式執行進度,一般會在某些數值卡住不動,引起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 } }
- 接收橫塊位置,生成縱塊位置,返回縱塊水平位置及垂直位置
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 } }
- 調整主函式,增加迴圈生成橫縱塊邏輯
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
如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。