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 vw
和 vh
值绕过了使用 %
导致的问题。
此用例在要转换的元素已根据窗口大小指定高度/宽度的情况下特别有用 - 例如如果元素是全角的 (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”比“百分比”更平滑/性能更好的主要内容,如果未能解决你的问题,请参考以下文章