- A+
所属分类:Web前端
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
引言
众所周知,进度条是程序员大大模拟的程序运行进度,一般会在某些数值卡住不动,引起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,下次一定。