CSS 转换过渡 - 使用“px”比“百分比”更平滑/性能更好

Posted

技术标签:

【中文标题】CSS 转换过渡 - 使用“px”比“百分比”更平滑/性能更好【英文标题】:CSS transform transition - using ´px´ more smooth/performant than 'percentage' 【发布时间】:2018-10-28 18:12:29 【问题描述】:

我最近一直在考虑改进我网站上的动画 - 更具体地说是移动设备上的导航下拉菜单。

在这方面,我偶然发现了以下案例,我希望对此有更深入的了解。

情况是,在转换/动画transform: translate3d() 时,似乎浏览器在使用% 而不是px 应用时需要更多计算。例如。在我的测试中,从transform: translate3d(0, 500px, 0)transform: translate3d(0,0,0) 的转换似乎比从transform: translate3d(0, 100%, 0) 转换需要更少的计算并且运行更顺畅。

更新:经过进一步测试,我发现使用100vh/100vw 可以绕过/缓解使用百分比的问题。这在元素具有已知的窗口百分比宽度或全宽的情况下很有用,从而提高了性能。实际上,使用这个值似乎就像在 Chrome 中分配了 px 值一样。

这是每个动画的时间轴图片。时间表是使用“性能”下的 Google 开发工具获得的。为了更好地展示差异,Chrome 开发工具中的性能已被限制为“低端移动”(6 倍 CPU 减速)。

使用百分比转换:

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

从图像中可以看出,当使用% 而不是px 来确定转换时,似乎正在进行更多的渲染和绘画。浏览器必须计算每一帧的百分比值是很有意义的(我猜?),但令我惊讶的是,与使用像素值相比,它需要更多的时间。

另外请注意,图片中显示百分比时间线的帧速率从未达到 ~60 fps,而是平均在 40 fps 左右。

下面是复制案例的sn-ps。一种使用百分比,一种使用 px。

$(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 值。对于生产情况,这自然需要在窗口大小调整时进行更新。

这种方法的最终时间表是:

$(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() 例如如果您“不能”使用% 并且同时想要流畅的动画,那么在屏幕外隐藏导航是非常偶然的。

【问题讨论】:

不确定是否为 100%,但我猜使用 % 值会使浏览器经常计算这些值,因为它可能会在动画期间发生变化,因为它是一个与 px 不同的相对值。一个很好的测试用例是让动画变慢,并在它调整浏览器大小并查看两者的性能。 你试过transform: translate3d(0, calc(100%), 0);我目前无法测试这个,但也许它会欺骗浏览器使用像素单位?! @yoshi 事实上我有,因为我相信与您的想法相同(calc () 会在浏览器中将其转换为 px 值),遗憾的是相同结果使用%。 Temani,这也是我的想法,也是个好主意 您是否尝试过添加 will-change: transform 规则? @vals 是的,这会产生与使用 % 的原始案例相同的结果。我猜这也是意料之中的,因为该元素已经通过使用 translate3d() “卸载”到 GPU 【参考方案1】:

我遇到这种行为是否正确(以 px 分配值的性能优于 %),如果是,为什么会发生这种情况?如前所述,它应该对我来说有点道理,但我真的缺乏一些技术/深入的解释。

确实是正确的。像素是绝对值(即不依赖于任何东西并“按原样”表示)。百分比是相对值,这意味着它们必须依赖于其他值才能产生结果。所以每次你分配一个百分比值时,它必须得到它的相对值来执行计算。 使用像素进行平移时,您只需更改平移值,但使用百分比时,您必须先获取元素的尺寸,然后再应用平移。这必须为每个动画帧完成。

为缓解此问题,您只需在动画之前重新计算元素的尺寸一次。然后使用!important 覆盖样式属性中设置的内容。 sn-p 中的代码正是这样做的。

另外请注意,我添加了一个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>

【讨论】:

【参考方案2】:

为了进一步扩展 CyperAP 给出的答案以及我在原始问题中提出的建议,我还发现使用 CSS3 vwvh 值绕过了使用 % 导致的问题。

此用例在要转换的元素已根据窗口大小指定高度/宽度的情况下特别有用 - 例如如果元素是全角的 (100%/100vw)。

根据原始问题中的示例,改用 transform: translate3d(0, 100vh, 0) 会产生以下时间线结果(同样,在 Chrome 中,性能仅限于“低端移动”):

片段可以在这里看到:

$(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>

【讨论】:

以上是关于CSS 转换过渡 - 使用“px”比“百分比”更平滑/性能更好的主要内容,如果未能解决你的问题,请参考以下文章

jQuery Transit 过渡效果

将具有混合(固定和百分比)值的 CSS 剪辑路径转换为 ​​SVG 剪辑路径

如何修复 CSS“过渡”取消悬停元素?

从 CSS 中的过渡中排除伪类

使用 CSS3 动画/过渡高度,但使用 jQuery?

如何使用 css 变换创建循环过渡动画