你为什么应该使用transform?

你或许听过别人说,transform 的性能更好,它不会触发重排,请使用 transfrom 来替代 position(left/right/top/bottom)
可他们之间的区别到底是什么?它为什么可以不触发重排呢?还有为什么 transform 会让其子元素的 z-index 失效呢?
希望本文可以解决你的这些疑问。

什么是重排和重绘

浏览器绘制图像的过程由两部分组成:

  • 主线程计算所有HTML元素的CSS样式、绘制位图,将位图发给合成线程,执行这个过程一般称为重排(relayout/reflow)
  • 合成线程调用GPU将这些位图组合(compositor layer),绘制到屏幕上。执行这个过程一般称为重绘(repaint)

什么情况下会触发重排

直接改变图形的样式会让浏览器频繁的进行重排(relayout)。重排意味着主线程需要计算所有HTML元素的位置,这是十分耗时的操作。

为什么要一定要进行重排呢?是因为默认的思路是这样的:一个图形的大小的变化会造成整个页面布局的变化。所以主线程应该计算整个页面的所有元素的位置,并将其发送给合成线程重新位置图像。

transform 做了什么?

从上面的解释我们可以看到,触发重排的条件是“重新布局”,如果一个元素位于它自己的作用域(即Stacking context层叠上下文)内,不影响其他任何元素的位置。那么理论上就可以避免重排,只将这个改变的元素进行重新绘制并和原有页面进行重新组合即可。

那么 position:absolute 可以实现让一个元素位于它自己的上下文内吗? 答案是不行,如果另一个元素与该元素的 z-index 相同,显然这个上下文并不是某个元素独有的。因此我们为一个绝对定位的元素添加动画时,依然会触发重排。例子见前端性能优化–transform与position .

终于,我们可以提到我们的主角 transform 了, transform 拥有自身独有的上下文。浏览器会为其单独创建一个复合层(composite layer),接下来页面的呈现就剩下合成线程与GPU的任务了。

具体情况下的 transform 表现

position:static 是元素不设置 position 时的默认布局,只设置 transform 的情况下,其功能类似于 position:relative + z-index : 0 ,即使元素以及离开初始定位,依然会占据初始定位的位置:

<!DOCTYPE html>


<html lang="en">






<head>


	<meta charset="UTF-8">


	<meta name="viewport" content="width=device-width, initial-scale=1.0">


	<title>Document</title>


	<style>


		body {

			margin: 0;

			padding: 0;

		}




		.a {

			transition: all 2s ease;

			transform: translate(0, 0);

			background: red;

		}





		.animation {
			transform: translate(100px, 100px);
		}
	</style>
</head>


<body>
	<div class="a">a</div>
	<div>b</div> 
	<button>click</button>
</body>
<script>
	const btn = document.querySelector("button")
	const a = document.querySelector(".a")
	btn.addEventListener("click", () => {
		a.classList.add("animation")
	})
</script>

</html>

1.gif

transform 元素和另一个绝对定位的元素重叠时,二者层级相同,可看作两者的层级均为 z-index : 0 ,堆叠顺序由两个元素的先后顺序决定:

<!DOCTYPE html>


<html lang="en">






<head>


	<meta charset="UTF-8">


	<meta name="viewport" content="width=device-width, initial-scale=1.0">


	<title>Document</title>


	<style>


		body {

			margin: 0;

			padding: 0;

		}




		.a {

			transition: all 2s ease;

			transform: translate(0, 0);

			background: red;

		}




		.c {
			position: absolute;
			top: 100px;
			left: 100px;
			background: rebeccapurple;
		}


		.animation {
			transform: translate(100px, 100px);
		}
	</style>
</head>

<body>
	<div class="a">a</div>
	<div class="c">c</div>
	<button>click</button>
</body>
<script>
	const btn = document.querySelector("button")
	const a = document.querySelector(".a")
	btn.addEventListener("click", () => {
		a.classList.add("animation")
	})
</script>

</html>

c 位于 a 之后,则其展示优先级高于 a

2.gif

a 位于 c 之后,则 a 的展示优先级高于 c

	<div class="c">c</div>
	<div class="a">a</div>

3.gif

如果拥有绝对布局的元素编写了 z-index:1 ,则其层级高于 transform 元素:

<style>

	...




	.c {
		...
		z-index: 1;
	}
</style>

<body>
	<div class="c">c</div>
	<div class="a">a</div>
</body>

2.gif

a 元素加上 position : absolute + z-index : 2 ,可以让其展示层级再次高于 c :

<style>

	.a {
		...
		position: absolute;
		z-index: 2;
	}	

	.c {
		...
		z-index: 1;
	}
</style>



<body>
	<div class="c">c</div>
	<div class="a">a</div>
</body>

4.gif

为什么 transform 子元素的 z-index 会失效?

假设我有一个 flex 布局的列表,每个元素执行了一些动画,动画结束后得到了 transform : translate(0,0) 的属性。元素内有个绝对定位的 tip 元素,它应该处于最高的层级,但实际上它会被右边的元素给遮挡:

5.gif
我们可以用一些简单的代码来还原这种情况:

<!DOCTYPE html>


<html lang="en">






<head>


	<meta charset="UTF-8">


	<meta name="viewport" content="width=device-width, initial-scale=1.0">


	<title>Document</title>


	<style>


		ul {
			list-style: none;
			display: flex;
			gap: 20px;
		}

		li {
			width: 100px;
			height: 100px;
			background-color: red;
			transform: translate(0, 0);
		}

		.tip{
			position: absolute;
			white-space: nowrap;
			background: #000;
			z-index: 9999;
		}
	</style>
</head>

<body>
	<ul>
		<li>
			<div class="tip">Proident eu non aliquip consequat dolore id laboris ut aliquip mollit.</di>
		</li>

		<li>
			<div class="tip"></div>
		</li>
	</ul>
</body>
<script>
</script>

</html>

Screenshot from 2023-07-22 20-37-36.png

这就是我们说的 z-index “失效”,其实说是“失效”其实不准确,这是因为 transform 元素拥有自身的上下文,其子元素的 z-index 只在这个上下文内有效,而其他元素的 z-index 在另一个层级中,所以看起来好像是 transform 子元素的 z-index “失效”了。

以上面这个例子举例,tipz-index : 9999 的定位只在第一个 li 内有效,而第一个 li 的层级和第二个 li 是相同的,相同层级下,后一个 li 的展示优先级更高,因此最终导致了最终的展示层级变为:第二个 li > 第一个 li (包括其中的 tip).

要解决这个问题有两种办法:

  1. 最直接的,当动画结束后,图形回到原位时,此时设置 translate(0,0) 是一件多余的事情,去掉 transform 属性即可。
  2. transform 元素设置 translateZ 属性,给其父元素设置 transform-style: preserve-3d; 属性,将其转化为 3d 形式,此时 absolute 定位的子元素会位于最高的层级上。(笔者没有找到原因,如果有知道的请在评论区告诉我,谢谢)
	<style>
		ul {
			...
			transform-style: preserve-3d;
		}

		li {
			...
			transform: translate(0, 0, 100px);
		}

		.tip {
			...
			position: absolute;
		}

	</style>
</head>



layouts 中看可以发现,即使 li 元素高于水平面100px, absolutetip 元素依然位于其层级之上:

Screenshot from 2023-07-22 21-26-01.png

总结

  • transform 拥有自身独立上下文是渲染优化的关键,它可以轻易的与原视图组合重绘而不用花费主线程大量计算时间进行重排。
  • transform 会使子元素 z-index 失效的原因是子元素的 z-index 位于 transform 布局的父容器之内,和其他的元素相互隔离。

参考:

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYdXO69R' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片