CSS变换过渡-使用'像素'比'百分比'更流畅/高效。

16

最近我一直在尝试提高网站动画效果,具体来说是移动设备上的导航下拉菜单。

在这个过程中,我发现了一个问题,希望能够深入了解一下。就是当使用%而不是px应用transform: translate3d()进行过渡/动画时,浏览器需要更多的计算。例如,在我的测试中,从transform: translate3d(0, 500px, 0)transform: translate3d(0,0,0)的转换需要更少的计算,并且运行更流畅,而从transform: translate3d(0, 100%, 0)的过渡则需要更多计算。

更新:进一步的测试表明,使用100vh/100vw可以避免/缓解使用百分比时出现的问题。这在元素具有已知窗口百分比宽度或为全宽度的情况下非常有用,可以提高性能。实际上,在 Chrome 中,使用此值的行为就像将其分配为px值。

以下是每个动画时间轴的几张图片。时间轴是在 Google Dev 工具下的“性能”选项卡中获取的。为了更好地显示性能差异,Chrome Dev 工具将性能限制为“低端移动设备”(6 倍 CPU 减速)。

使用百分比进行变换:

Transform performance using percent (%)

使用像素 (px) 进行转换:

Transform performance using pixel (px)

从这些图片中可以看出,使用%来确定变换时,渲染和绘画的工作量要比使用px更多。浏览器必须为每个帧计算百分比值(我猜?),这很有道理,但与使用像素值相比,它需要更多的工作量,这让我感到惊讶。
另外请注意,显示百分比时间轴的图片中帧率从未达到60 fps,而是平均约40 fps。
以下是复制此情况的代码片段。其中一个使用百分比,另一个使用像素。

$(document).on("click", function(){
$(".bb").toggleClass("active");
});
.aa{
  height:50px;
  background:blue;
  position:fixed;
  top:0;
  width:100%;

}
.bb{
  position:fixed;
  top:0px;
  background:none;
  height:100%;
  width:100%;
  left:0;
  transform:translateZ(0);
  overflow:hidden;
    pointer-events:none;
}
.cc{
  height:100%;
    transform:translate3d(0,500px,0);
    width:100%;
      transition:transform .5s ease-in;
      background:red;
}
.bb.active .cc{
  transform:none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Click the document to start animation<p>
<div class="bb">
<div class="cc">
<ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul><ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul>

</div>

</div>

$(document).on("click", function(){
$(".bb").toggleClass("active");
});
.aa{
  height:50px;
  background:blue;
  position:fixed;
  top:0;
  width:100%;

}
.bb{
  position:fixed;
  top:0px;
  background:none;
  height:100%;
  width:100%;
  left:0;
  transform:translateZ(0);
  overflow:hidden;
    pointer-events:none;
}
.cc{
  height:100%;
    transform:translate3d(0,100%,0);
    width:100%;
      transition:transform .5s ease-in;
      background:red;
}
.bb.active .cc{
  transform:none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Click the document to start animation<p>
<div class="bb">
<div class="cc">
<ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul><ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul>

</div>

</div>

为解决在使用transform时可能会导致动画性能变差的问题,我提出了以下建议,可能会改善性能。但是,由于不确定是否必要以及原因,因此我很感兴趣听到其他意见。
我所做的基本上只是使用jQuery将transform值应用于像素而不是百分比。对于生产情况,这自然需要在窗口调整大小时更新。
此方法的结果时间表如下:

enter image description here

$(document).ready(function(){
var bbWidth = $("#bb .cc").css('transform').split(',')[5].slice(0,-1);

$("#bb .cc").css("transform", "translate3d(0," + bbWidth + "px,0");
$(document).on("click", function(){
$("#bb").toggleClass("active");
});
});
.aa{
  height:50px;
  background:blue;
  position:fixed;
  top:0;
  width:100%;

}
#bb{
  position:fixed;
  top:0px;
  background:none;
  height:100%;
  width:100%;
  left:0;
  transform:translateZ(0);
  overflow:hidden;
}
.cc{
  height:100%;
    transform:translate3d(0,100%,0);
    width:100%;
      transition:transform .5s ease-in;
      background:red;
      position:absolute;
      top:0;
}
#bb.active .cc{
  transform:none!important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="bb">
<div class="cc">
<ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul><ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul>

</div>

</div>

问题:

  • 我的经验是使用px赋值比使用%赋值性能更好,这种行为是否正确?如果是,为什么会发生这种情况?正如之前提到的,我认为这应该是有道理的,但我真的缺乏一些技术/深入的解释。
  • 除了我的建议,是否有更好的方法来规避这个问题?如果你“不能”使用%并且想要同时拥有平滑的动画效果,那么使用transform: translate()将导航隐藏在屏幕外是非常具体的。

我不确定是否正确,但我猜测使用百分比值会使浏览器在动画期间经常计算这些值,因为它是相对值,而不像像素值那样固定。一个好的测试案例是将动画变慢,并在其期间调整浏览器大小,看看两者的性能如何。 - Temani Afif
你尝试过使用 transform: translate3d(0, calc(100%), 0); 吗?我目前无法测试,但也许它可以欺骗浏览器使用像素单位?! - Yoshi
3
事实上,我也有过这样的想法,就像你一样,认为 calc() 会在浏览器中将其转换为像素值,但不幸的是结果和使用百分比一样。Temani,这也是我的想法,是个好主意。 - Chri.s
你是否尝试过添加will-change: transform规则? - vals
1
@vals 是的,这与使用“%”的原始情况产生相同的结果。我想这也是可以预料的,因为使用 translate3d() 已经将元素“卸载”到 GPU 上了。 - Chri.s
2个回答

2
“我经历这个行为(用px赋值比%好)是正确的,如果是这样,为什么会发生呢?正如前面提到的,这在某种程度上对我来说有些合理,但我真的缺乏一些技术/深入的解释。”
“确实如此。像素是绝对值(即不依赖任何东西并且表示为‘原样’)。百分比是相对值,这意味着它们必须依赖于其他某个值才能产生结果。因此,每次分配百分比值时,它都必须获取其相对值才能执行计算。”
“使用像素进行翻译时,您只需更改翻译值,但使用百分比时,您必须首先获得元素的尺寸,然后应用翻译。而且每个动画帧都必须这样做。”
“要解决这个问题,您只需在动画之前重新计算元素的尺寸即可。然后使用 !important 覆盖样式属性中设置的内容。片段中的代码正是如此。”
“请注意,我添加了一个 resize 侦听器。如果窗口大小调整,这是必需的,因此您的元素变为隐藏状态。”

$(function(){
var $el = $("#bb");
$(document).on("click", function(){
  var height = $el.outerHeight();
  $el
    .css('transform', 'translateY(' + height + 'px)')
    .toggleClass("active");
});
$(window).on('resize', function() {
  $el.removeClass('active').removeAttr('style');
});
});
#bb{
  position:fixed;
  top:0px;
  background-color: red;
  height:100%;
  width:100%;
  left:0;
  overflow:hidden;
  transform: translateY(100%);
  transition: transform .5s ease-in;
}
#bb.active
{
  transform: translateY(0px) !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="bb">
<div class="cc">
<ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul><ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul>

</div>

</div>


0
进一步扩展CyperAP的答案和我原来问题中提出的建议,我还发现使用CSS3的vwvh值可以避免使用%时出现的问题。
在元素基于窗口大小设置高度/宽度的情况下,这种用例尤其有用 - 例如,如果元素是全宽(100%/100vw)。
基于原始问题的示例,而是使用transform: translate3d(0, 100vh, 0),会产生以下时间轴结果(同样,在Chrome中性能受到“低端移动设备”的限制):

enter image description here

代码片段可以在这里看到:

$(document).on("click", function(){
$(".bb").toggleClass("active");
});
.aa{
  height:50px;
  background:blue;
  position:fixed;
  top:0;
  width:100%;

}
.bb{
  position:fixed;
  top:0px;
  background:none;
  height:100%;
  width:100%;
  left:0;
  transform:translateZ(0);
  overflow:hidden;
    pointer-events:none;
}
.cc{
  height:100%;
    transform:translate3d(0,100vh,0);
    width:100%;
      transition:transform .5s ease-in;
      background:red;
}
.bb.active .cc{
  transform:none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Click the document to start animation<p>
<div class="bb">
<div class="cc">
<ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul><ul>
<li>Point one</li>
<li>Point two</li>
<li>Point three</li>
<li>Point four</li>
<li>Point five</li>
<li>Point six</li>
<li>Point seven</li>
</ul>

</div>

</div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接