为什么转换框阴影会导致整页重绘?

Why does transitioning the box shadow cause a full page repaint?

我注意到当我将鼠标悬停在带有动画 box-shadow 的元素上时,我的页面出现了滞后。使用 Chrome 的 Devtools,我注意到当我将鼠标悬停在该元素上时整个页面都在重新绘制。重绘花费了 40 多毫秒,或大约 3 帧。 transition 持续大约半秒,因此在半秒内有明显的滞后。

如何将重绘限制在只有框阴影的区域?

这是一个演示:http://jsfiddle.net/8sa41xfL/

html,body{
    height:100%;
}

#test{
    background:red;
    height:100px;
    width:200px;
    transition:box-shadow 0.5s;
}

#test:hover{
    box-shadow:0 0 3px 3px rgba(0,0,0,0.3);
}
<div id=test></div>

transform:translateZ(0) 在我的页面上不起作用,但在 fiddle 上有效。除了 transform:translateZ(0) 之外还有其他修复方法吗?

CSS 框阴影的绘制成本很高。阅读有关 SO here.

的更多信息

如果您想避免整页重绘,请在您的元素上使用 position:absolute。这将重新绘制元素周围的区域,而不会影响整个页面。 Fiddle.

如 Pierre 的回答中链接的线程中所述,box-shadow 绘画成本很高。解释它为什么昂贵需要深入了解渲染的工作方式,而我没有足够的知识来完全解释它。但是这个答案试图解释为什么整个页面被重新绘制以及避免它的各种可能方法。


根据CSS Triggers website:

Changing box-shadow does not trigger any geometry changes, which is good. But since it is a visual property, it will cause painting to occur. Painting is typically a super expensive operation, so you should be cautious.

Once any pixels have been painted the page will be composited together.


为什么每次都重绘整个页面?

以下文章解释了绘画在高层次上的实际工作方式:

根据这些文章,我们可以看到 DOM 树中产生视觉输出的每个节点都被视为 RenderObject,并且每个 RenderObject 直接或间接地是 RenderLayer 的一部分。每当发生更改时,渲染器(或渲染对象)都会使其在屏幕上的矩形(或渲染层)无效并触发重绘。

在这种情况下,整个页面似乎都在重新绘制,因为 #test 元素不保证创建单独的 RenderLayer(基于 Chromium 项目文章中提到的标准),因此变成了根渲染层的一部分。因为它是根渲染层的一部分,所以每次需要重绘时整个页面都会被重绘。

下面的片段证明了上面的断言是正确的。在这里,我添加了一个 #cover 元素(带定位)来包围 #test 元素。现在,由于 #cover 元素具有显式定位,它在根层之上创建了一个额外的层,并且 #test 成为该中间层的一部分。现在,我们可以看到 box-shadow 过渡只重绘了这个中间层,而不是整个页面。

html,
body {
  height: 100%;
}
#cover {
  position: relative;
}
#test {
  background: red;
  height: 100px;
  width: 200px;
  transition: box-shadow 0.5s;
}
#test:hover {
  box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
}
<div id=cover>
  <div id=test></div>
</div>


解决方法是什么?

有多种 CSS 属性可用于解决此问题,但它们似乎都指向较高级别的同一点 - 即为 [= 创建一个单独的渲染层19=]元素.

以下是为 #test 元素创建单独渲染层的几个可能选项:

  • 通过添加显式位置属性 - 这与 Pierre 的回答中描述的选项相同,但 absolute 定位不是唯一的选项。即使 relative 定位也能解决。

    html,
    body {
      height: 100%;
    }
    #test {
      position: relative;
      background: red;
      height: 100px;
      width: 200px;
      transition: box-shadow 0.5s;
    }
    #test:hover {
      box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
    }
    
    <div id=test></div>
    

  • 通过添加透明度(不透明度)- 浏览器似乎甚至将 opacity: 0.99 视为添加透明度并且它非常有用,因为添加它不会导致任何视觉差异。

    html,
    body {
      height: 100%;
    }
    #test {
      background: red;
      height: 100px;
      width: 200px;
      opacity: 0.99;
      transition: box-shadow 0.5s;
    }
    #test:hover {
      box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
    }
    
    <div id=test></div>
    

  • 通过添加一个虚拟 CSS 过滤器 - 我们可以添加一个 filter: blur(0px),因为它什么都不做。

    html,
    body {
      height: 100%;
    }
    #test {
      background: red;
      height: 100px;
      width: 200px;
      -webkit-filter: blur(0px);
      filter: blur(0px);
      transition: box-shadow 0.5s;
    }
    #test:hover {
      box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
    }
    
    <div id=test></div>