优化:will-change

202 阅读2分钟

1、浏览器渲染页面的过程

浏览器渲染页面的过程大致为:html解析,样式计算,布局,分层,绘制,分块,光栅化,画

2、will-change作用于哪个阶段

will-change是在分层阶段告诉浏览器这个元素后面要发生一些复杂的动画,你可以提前做好准备,提前分配一些资源,将当前元素单独分为一层,将来元素发生变化的时候,可以更高效的处理这个元素,而不需要计算整个渲染树

理解分层的概念

写一个简单的动画:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>will-change</title>
    <style>
      .box {
        width: 100px;
        height: 100px;
        background-color: lightblue;
        transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
      }
      .move {
        transform: translateX(300px);
        opacity: 0.5;
      }
    </style>
  </head>
  <body>
    <button id="btn">Toggle Move</button>
    <div class="box" id="box"></div>
    <script>
      btn.addEventListener('click', () => {
        box.classList.toggle('move')
      })
    </script>
    <script></script>
  </body>
</html>

效果:

动画.gif

可以看出,当动画开始执行的时候,浏览器是为box容器分了一个层,来进行动画的渲染

当我们给box容器设置上will-change: transform, opacity;,浏览器在渲染box元素时就已经给分好层了,这样在后面进行动画时,浏览器只需要针对这个层进行渲染,效率得到提高

动画.gif

这样看更明显一点:

动画.gif

3、分层越多越好吗

即使是淘宝这么复杂的页面,分层也就这几个

image.png

使用will-change的注意事项:

  1. 不要将will-change用于过多的元素,浏览器已经尽力优化了所有东西,will-change在分层的过程中会消耗大量的机器资源
  2. 浏览器的优化应该是尽可能短时间内删除优化,恢复到正常状态,如果将will-change设置到样式表中,意味着动画结束后,浏览器会保留will-change更长时间,这与优化的初衷不符。最好的做法是更改发生之前和之后使用js开启和关闭will-change
  3. will-change应该被视为最后的优化手段,不应该用来预测一些可能会发生的性能问题。也就是说,不主动使用,当页面发生卡顿时,可以考虑使用will-change来进行分层

4、will-change的最佳实践-示例

  1. 不主动使用will-change
  2. 当出现性能问题时,参考以下例子添加和移除will-change
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>will-change</title>
    <style>
      .box {
        width: 100px;
        height: 100px;
        background-color: lightblue;
        transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
        /* 提前告知即将改变的属性 */
        /* will-change: transform, opacity; */
      }
      .move {
        transform: translateX(300px);
        opacity: 0.5;
      }
    </style>
  </head>
  <body>
    <button id="btn">Toggle Move</button>
    <div class="box" id="box"></div>
    <script>
      const btn = document.getElementById('btn')
      const box = document.getElementById('box')
      btn.addEventListener('click', () => {
        box.classList.toggle('move')
        // 每次动画开始时,重新添加监听器
        box.addEventListener('transitionend', removeHint, { once: true })
      })

      // 当鼠标移动到该元素上时给该元素设置 will-change 属性
      btn.addEventListener('mouseenter', hintBrowser)

      function hintBrowser() {
        // 将在动画关键帧块中发生变化的可优化属性
        box.style.willChange = 'transform, opacity'
      }

      function removeHint() {
        box.style.willChange = 'auto'
      }
    </script>
  </body>
</html>