記錄一次實踐,看看小程式購物車動畫怎麼優化

青灯夜游
發布: 2021-12-28 10:21:49
轉載
2316 人瀏覽過

這篇文章跟大家分享一次小程式動畫優化實踐,看看小程式購物車動畫怎麼優化,希望對大家有幫助!

記錄一次實踐,看看小程式購物車動畫怎麼優化

小程式購物車動畫優化

公司小程式點擊加購時,會繪製一個拋物線動畫,這個拋物線動畫是計算出來的貝塞爾曲線上各點的座標,再由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(&#39;con&#39;);
con.onclick = function () {
  Promise.resolve().then(function Promise1 () {
    con.textContext = 0;
  })
};
登入後複製

記錄一次實踐,看看小程式購物車動畫怎麼優化

// demo2
// 两次EventLoop中间没有渲染
const con = document.getElementById(&#39;con&#39;);
con.onclick = function () {
  setTimeout(function setTimeout1() {
      con.textContent = 0;
      Promise.resolve().then(function Promise1 () {
          console.log(&#39;Promise1&#39;)
    })
  }, 0)
  setTimeout(function setTimeout2() {
    con.textContent = 1;
    Promise.resolve().then(function Promise2 () {
        console.log(&#39;Promise2&#39;)
    })
  }, 0)
};
登入後複製

記錄一次實踐,看看小程式購物車動畫怎麼優化

我們知道瀏覽器正常情況下的幀率是60fps,即一幀的時間大約是16.66ms。如果在一幀裡對Dom進行了兩次修改,那麼瀏覽器只會取最後一次的修改值去渲染。

// demo3
// 两次eventloop中有渲染
const con = document.getElementById(&#39;con&#39;);
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(&#39;btn&#39;)
btn.addEventListener(&#39;click&#39;, 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(&#39;setInterval&#39;, gap, countIdx)
    if (countIdx > 5) clearInterval(timer)
    // 10000000
    // 100000
    for (let i = 0; i < 100000; i++) {
      sum+= i
    }
  }, 100)
  last = new Date().getTime()
}
登入後複製

記錄一次實踐,看看小程式購物車動畫怎麼優化

setInterval的缺點:

    當主執行緒有程式碼執行時,巨集任務佇列會一直按照一定的時間間隔推入事件回呼函數。只有當主執行緒空閒時,才會執行回呼函數,但是這些回呼函數大多都是過時的。
  • 如果setInterval的回呼間隔比瀏覽器渲染一幀的時間要短,那麼回呼函數執行了多次,但只會用到最後一次的結果,這樣也會造成浪費,甚至有可能會阻塞主線程。
所以盡量用setTimeout去代替setInterval

#使用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(&amp;#39;con&amp;#39;); let i = 0; function rAF(){ requestAnimationFrame(function aaaa() { con.textContent = i; Promise.resolve().then(function bbbb(){ if(i &lt; 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(&#39;.cart-animation&#39;).setStyle({
      display: &#39;block&#39;,
      left: coordArr[curCoordIdx].x + &#39;px&#39;, 
      top: coordArr[curCoordIdx].y + &#39;px&#39;
    })
    curCoordIdx -= 1
    ins.requestAnimationFrame(setStyleByFrame)
  } else {
    ins.selectComponent(&#39;.cart-animation&#39;).setStyle({
      display: &#39;none&#39;
    })
  }
}
登入後複製

在真机上效果非常明显,低端安卓机上的动画也非常丝滑。但是录屏效果不好,这里就不放了。

【相关学习推荐:小程序开发教程

以上是記錄一次實踐,看看小程式購物車動畫怎麼優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板