🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
引言
众所周知,进度条是程序员大大模拟的程序运行进度,一般会在某些数值卡住不动,引起99%悬案。但是背后的原理你真的清楚吗,其实进度条真的是匀速运动的!
先来看看效果
接下来开始实现
创建一个矩形,然后折叠起来,完成!
创建一个容器,用于宽度限制
折叠形状实际大小肯定是大于需求大小的,在这得用绝对定位限制,防止影响到页面布局。将每一段用block展示,后续使用3d变换折叠起来。
// stylus .outsideboxposition relativeheight 20px.loading-bar display flexposition absolutetransform-style preserve-3dheight 20px.blockheight 100%background #000f2e<div class="outsidebox" :style="{width: `${showWidth}px`}"><div class="loading-bar"><div class="block"></div></div> </div>
建立主函数,接收参数为最终可视宽度
因折叠块起始是水平块,所以限制块个数为奇数,以横竖横方式生成。 艺术就是派大星! 随机数!块的宽度采用随机比值的方式生成,通过横向的比值和逆推实际总宽度。
const createRandomRatio = (num: number) => {// 创建随机比值,合为1let 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 * -directdata.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 0let 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
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。