### [记录一次实践,看看小程序购物车动画怎么优化](https://www.prosaas.cn/article/16326) **Published:** 2021-12-28T02:21:49 **Author:** 5201314 **Excerpt:** 本篇文章给大家分享一次小程序动画优化实践,看看小程序购物车动画怎么优化,希望对大家有所帮助!小程序购物车动画优… 本篇文章给大家分享一次小程序动画优化实践,看看小程序购物车动画怎么优化,希望对大家有所帮助! ![](https://img.php.cn/upload/article/000/000/024/61ca7440ec5f4754.jpg) ## 小程序购物车动画优化 > 公司小程序点击加购时,会绘制一个抛物线动画,这个抛物线动画是计算出来的贝塞尔曲线上每个点的坐标,再由js遍历点坐标,然后动态设置点的样式,从而实现动画。但这会带来卡顿掉帧问题 this.goodBoxTimer = setInterval(() => { index-- this.setData({ 'movingBallInfo.posX': linePos\[index\]\[0\], 'movingBallInfo.posY': linePos\[index\]\[1\], }) if (index < 1) { this.resetGoodBoxStatus() } }, 30) **前置知识:Event Loop, Task, micro Task, UI Rendering** javascript是单线程语言,这就意味着所有任务都要进行排队。任务分为两种:一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。 而异步任务又分为宏任务(Task)和微任务(micro Task),同理任务队列也分为宏任务队列和微任务队列。 事件循环(Event Loop) 大致步骤: - 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 - 只要异步任务有了运行结果,就在任务队列之中放置一个事件。 - 执行栈中的宏任务执行完毕,引擎会先读取微任务,推入执行栈。执行完成之后,继续读取下一个微任务。如果执行过中产生新的微任务,就会把这个微任务推入微任务队列。如果主线程执行完所有微任务队列中的任务中时,就会去读取宏任务队列,推入执行栈。 - 主线程不断重复上面的第三步。 常见的宏任务: - setTimeout - setInterval - postMessage - ... 常见的微任务: - Promise - MutationObserver 而Event Loop和UI渲染的关系呢?其实是在执行完微任务队列里所有微任务的之后,由浏览器决定是否进行渲染更新。 // demo1 // 渲染发生在微任务之后 const con = document.getElementById('con'); con.onclick = function () { Promise.resolve().then(function Promise1 () { con.textContext = 0; }) }; ![](https://img.php.cn/upload/image/845/866/914/1640657702502102.png) // demo2 // 两次EventLoop中间没有渲染 const con = document.getElementById('con'); con.onclick = function () { setTimeout(function setTimeout1() { con.textContent = 0; Promise.resolve().then(function Promise1 () { console.log('Promise1') }) }, 0) setTimeout(function setTimeout2() { con.textContent = 1; Promise.resolve().then(function Promise2 () { console.log('Promise2') }) }, 0) }; ![](https://img.php.cn/upload/image/191/447/768/1640657715167189.png) 我们知道浏览器正常情况下的帧率是60fps,即一帧的时间大约为16.66ms。如果在一帧里对Dom进行了两次修改,那么浏览器只会取最后一次的修改值去渲染。 // demo3 // 两次eventloop中有渲染 const con = document.getElementById('con'); con.onclick = function () { setTimeout(function setTimeout1() { con.textContent = 0; }, 0); setTimeout(function setTimeout2() { con.textContent = 1; }, 16.7); }; ![](https://img.php.cn/upload/image/256/137/582/1640657728900533.png) **尽量不要使用setInterval** 由上文可知setInterval是宏任务,setInterval每隔定义的时间间隔就会往宏任务队列推入回调函数,然后主线程会读取宏任务队列里的setInterval回调函数并执行。但是如果主线程有长任务(long task)执行时,会阻塞读取,直到主线程里的任务执行完才会继续读取,但setInterval往宏任务队列添加回调函数的操作是不会停止的,这种情况下就会造成:函数执行的时间间隔远大于我们定义的时间间隔。 下面是一个例子,每次setInterval回调都需要进行大量的计算,这样阻塞主线程 // demo4 const btn = document.getElementById('btn') btn.addEventListener('click', setIntervalFn) let sum = 0 function setIntervalFn() { let last let countIdx = 0 const timer = setInterval(function timeFn() { countIdx++ const newTime = new Date().getTime() const gap = newTime - last last = newTime console.log('setInterval', gap, countIdx) if (countIdx > 5) clearInterval(timer) // 10000000 // 100000 for (let i = 0; i < 100000; i++) { sum+= i } }, 100) last = new Date().getTime() } ![](https://img.php.cn/upload/image/176/431/251/1640657815926480.png) setInterval的缺点: - 当主线程有代码执行时,宏任务队列会一直按照一定的时间间隔推入事件回调函数。只有当主线程空闲时,才会把回调函数执行,但是这些回调函数大多都是过时的。 - 如果setInterval的回调间隔比浏览器渲染一帧的时间要短,那么回调函数执行了多次,但只会用到最后一次的结果,这样也会造成浪费,甚至有可能会阻塞主线程。 所以尽量要用setTimeout去代替setInterval **使用requestAnimationFrame** 如果用js去绘制动画,还是用官方推荐的requestAnimationFrame,而不是setTimeout。 > `window.requestAnimationFrame()` 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画 由上面的例子可知,两个宏任务之间不一定会触发浏览器渲染,这个由浏览器自己决定,并且浏览器的帧率并会一直是60fps,有时可能会下降到30fps,而setTimeout的回调时间是写死的,就有可能导致修改了多次Dom,而只触发了一次ui更新,造成掉帧。 // demo5 const con = document.getElementById('con'); let i = 0; function rAF(){ requestAnimationFrame(function aaaa() { con.textContent = i; Promise.resolve().then(function bbbb(){ if(i < 5) {rAF(); i++;} }); }); } con.onclick = function () { rAF(); }; ![](https://img.php.cn/upload/image/412/705/313/1640657854778507.png) 可以看到渲染了5次(五条竖直虚线) **小程序上的动画优化** 小程序是双线程架构 ![](https://img.php.cn/upload/image/416/262/830/1640657858530582.png) 好处是:ui渲染和js主线程是分开的,我们知道在浏览器中这两者是互斥的,所以当主线程有阻塞时,页面交互就会失去响应,而小程序中不会出现这样的情况。 坏处是:逻辑层、渲染层有通信延时,大量的通信也会造成性能瓶颈。 小程序提供了wxs用来处理渲染层的逻辑。 **购物车抛物线动画优化** 所以我们不应该用setInterval去执行动画,我们修改成,当点击加购时,把点击坐标与目标坐标传入`wxs`,然后计算运行轨迹点的坐标计算,接着用`requestAnimationFrame`执行动画帧 // wxs function executeCartAnimation () { curCoordIdx = coordArr.length - 1 ins.requestAnimationFrame(setStyleByFrame) } function setStyleByFrame() { if (curCoordIdx >= 0) { ins.selectComponent('.cart-animation').setStyle({ display: 'block', left: coordArr\[curCoordIdx\].x + 'px', top: coordArr\[curCoordIdx\].y + 'px' }) curCoordIdx -= 1 ins.requestAnimationFrame(setStyleByFrame) } else { ins.selectComponent('.cart-animation').setStyle({ display: 'none' }) } } 在真机上效果非常明显,低端安卓机上的动画也非常丝滑。但是录屏效果不好,这里就不放了。 【相关学习推荐:小程序开发教程】 以上就是记录一次实践,看看小程序购物车动画怎么优化的详细内容,更多请关注知企PROSAAS其它相关文章! **Tags:** dom, Event, JS, ui, 主线程, 事件, 回调函数, 异步, 循环, 架构 **Categories:** 后端开发 ---