這篇文章跟大家分享一次小程式動畫優化實踐,看看小程式購物車動畫怎麼優化,希望對大家有幫助!
公司小程式點擊加購時,會繪製一個拋物線動畫,這個拋物線動畫是計算出來的貝塞爾曲線上各點的座標,再由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)。
只要非同步任務有了運行結果,就在任務佇列之中放置一個事件。
執行堆疊中的巨集任務執行完畢,引擎會先讀取微任務,並推入執行堆疊。執行完成之後,繼續讀取下一個微任務。如果執行過中產生新的微任務,就會把這個微任務推入微任務佇列。如果主執行緒執行完所有微任務佇列中的任務中時,就會去讀取巨集任務佇列,推入執行堆疊。
主執行緒不斷重複上面的第三步。
常見的巨集任務:
// demo1 // 渲染发生在微任务之后 const con = document.getElementById('con'); con.onclick = function () { Promise.resolve().then(function Promise1 () { con.textContext = 0; }) };
// 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) };
// 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); };
#盡量不要使用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() }
#使用requestAnimationFrame##如果用js去繪製動畫,還是用官方推薦的requestAnimationFrame,而不是setTimeout。
window.requestAnimationFrame()告訴瀏覽器-你希望執行一個動畫,並且要求瀏覽器在下次重繪之前呼叫指定的回呼函數更新動畫
</blockquote><p>由上面的例子可知,两个宏任务之间不一定会触发浏览器渲染,这个由浏览器自己决定,并且浏览器的帧率并会一直是60fps,有时可能会下降到30fps,而setTimeout的回调时间是写死的,就有可能导致修改了多次Dom,而只触发了一次ui更新,造成掉帧。</p><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">// demo5 const con = document.getElementById(&#39;con&#39;); 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(); };</pre><div class="contentsignin">登入後複製</div></div><p><img src="https://img.php.cn/upload/image/412/705/313/1640657854778507.png" title="1640657854778507.png" alt="記錄一次實踐,看看小程式購物車動畫怎麼優化"/></p><p>可以看到渲染了5次(五条竖直虚线)</p><p><span style="max-width:90%"><strong>小程序上的动画优化</strong></span></p><p>小程序是双线程架构</p><p><img src="https://img.php.cn/upload/image/416/262/830/164065785853058記錄一次實踐,看看小程式購物車動畫怎麼優化" title="164065785853058記錄一次實踐,看看小程式購物車動畫怎麼優化" alt="記錄一次實踐,看看小程式購物車動畫怎麼優化"/></p><p>好处是:ui渲染和js主线程是分开的,我们知道在浏览器中这两者是互斥的,所以当主线程有阻塞时,页面交互就会失去响应,而小程序中不会出现这样的情况。</p><p>坏处是:逻辑层、渲染层有通信延时,大量的通信也会造成性能瓶颈。</p><p>小程序提供了wxs用来处理渲染层的逻辑。</p><p><span style="max-width:90%"><strong>购物车抛物线动画优化</strong></span></p><p>所以我们不应该用setInterval去执行动画,我们修改成,当点击加购时,把点击坐标与目标坐标传入<code>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' }) } }登入後複製在真机上效果非常明显,低端安卓机上的动画也非常丝滑。但是录屏效果不好,这里就不放了。
【相关学习推荐:小程序开发教程】
以上是記錄一次實踐,看看小程式購物車動畫怎麼優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!