为啥过渡框阴影会导致整页重绘?

Posted

技术标签:

【中文标题】为啥过渡框阴影会导致整页重绘?【英文标题】:Why does transitioning the box shadow cause a full page repaint?为什么过渡框阴影会导致整页重绘? 【发布时间】:2015-10-23 18:44:43 【问题描述】:

当我将鼠标悬停在带有动画 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) 在我的页面上不起作用,但它在小提琴中起作用。除了transform:translateZ(0),还有其他解决方法吗?

【问题讨论】:

在 Iceweasel (Firefox) 中看不到重绘/滞后。 我在 JSFiddle 和 Chrome 上的内联 sn-p 中都看到了重绘,可能只是一个 Chrome 错误。 @BlagoEreš:是的(在 Chrome 上)。从 Chrome 开发工具中激活“显示绘制矩形”选项,然后查看每次都重新绘制的区域。您很可能会看到一个红色/绿色闪烁的矩形(取决于 Chrome 版本)。 它现在不会用 chrome 重新绘制整个页面,(版本 64) 【参考方案1】:

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


根据CSS Triggers website:

改变 box-shadow 不会触发任何几何变化,这很好。但由于它是一种视觉属性,它会导致绘画发生。绘画通常是一项超级昂贵的操作,因此您应该小心。

一旦绘制了任何像素,页面将被合成在一起。


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

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

HTML5 Rocks - How Browsers Work - Painting The Chromium Project - GPU accelerated rendering in Chrome

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

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

后面的sn-p证明了上面的断言是正确的。在这里,我添加了一个#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 属性可用于解决此问题,但它们似乎都指向同一点,即为#test 元素创建单独的渲染层。

以下是为#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);
&lt;div id=test&gt;&lt;/div&gt;

通过添加透明度(不透明度) - 浏览器似乎甚至将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);
&lt;div id=test&gt;&lt;/div&gt;

通过添加一个虚拟 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);
&lt;div id=test&gt;&lt;/div&gt;

【讨论】:

【参考方案2】:

CSS 盒子阴影的绘制成本很高。阅读更多关于 SO here。

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

【讨论】:

以上是关于为啥过渡框阴影会导致整页重绘?的主要内容,如果未能解决你的问题,请参考以下文章

CSS3 过渡缓入和缓出框阴影

为啥背景颜色与框阴影重叠?

为啥我的3Dmax渲染不出来阴影?

前端之动画与阴影

动画及阴影

为啥过滤器(阴影)导致我的 SVG 在 Safari 中消失?