• 技术文章 >微信小程序 >小程序开发

    记录一次实践,看看小程序购物车动画怎么优化

    青灯夜游青灯夜游2021-12-28 10:21:49转载342
    本篇文章给大家分享一次小程序动画优化实践,看看小程序购物车动画怎么优化,希望对大家有所帮助!

    小程序购物车动画优化

    公司小程序点击加购时,会绘制一个抛物线动画,这个抛物线动画是计算出来的贝塞尔曲线上每个点的坐标,再由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) 大致步骤:

    常见的宏任务:

    常见的微任务:

    而Event Loop和UI渲染的关系呢?其实是在执行完微任务队列里所有微任务的之后,由浏览器决定是否进行渲染更新。

    // demo1
    // 渲染发生在微任务之后
    const con = document.getElementById('con');
    con.onclick = function () {
      Promise.resolve().then(function Promise1 () {
        con.textContext = 0;
      })
    };

    1.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)
    };

    2.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);
    };

    3.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()
    }

    4.png

    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();
    };

    5.png

    可以看到渲染了5次(五条竖直虚线)

    小程序上的动画优化

    小程序是双线程架构

    6.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'
        })
      }
    }

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

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

    以上就是记录一次实践,看看小程序购物车动画怎么优化的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:小程序 动画优化
    上一篇:浅谈小程序确保每个页面都已登陆的方法 下一篇:浅析小程序中如何优雅地进行模块化处理?
    VIP课程(WEB全栈开发)

    相关文章推荐

    • 【腾讯云】年中优惠,「专享618元」优惠券!• 小程序中怎么进行父子组件传值和方法调用?(方法汇总)• 浅谈怎么使用小程序实现一个变速大转盘• 小程序中如何优雅的捕捉异步方法的异常• React如何构建小程序?两种实现方案分享• 浅谈小程序确保每个页面都已登陆的方法
    1/1

    PHP中文网