转自《Managing Z-Index In A Component-Based Web Application》
二货翻译:范小饭
Managing Z-Index In A Component-Based Web Application
quick summary:The z-index property, despite all that’s written about it, is still widely misunderstood and mishandled. Stacking issues in a complex single-page web application can become a major pain. Adhering to some principles, however, we can easily avoid these issues.
在基于组件化的web应用程序中神奇的z-index
快速摘要:尽管已经写了这么多关于z-index属性的内容,它仍然广泛的被无解和处理不当。在一个复杂的单页web应用程序中,堆叠问题是一个主要的痛,但是,坚持一些规则,我们可以很容易的避免这些问题。
If you’ve done any complex web UI development, you must have at least once furiously tried driving an element’s z-index up to thousands, only to see that it’s not helping position it on top of some other element, whose z-index is lower or even not defined at all.
如果你以前做过一些复杂的web页面开发,你肯定不止一次狂躁的把一个元素的z-index提升到数千,只是看到它没有定位在其他元素上,那些元素的z-index很低甚至根本没有定义。
Why does that happen? And more importantly, how to avoid such issues?
发生了什么?更重要的是,怎么去避免这样的问题?
In this article, I’ll recap what z-index actually is and how you can stop guessing whether it might work in any specific case and start treating it just like any other convenient tool.
在这篇文章中,我会扼要重述z-index到底是什么,你如何停止猜测它在特定场景中是否可以工作,并开始像对待其他方便的工具一样对待它。
The Hierarchy Of Stacking Contexts
If you imagine the webpage as having three dimensions, then z-index is a property that defines the z coordinate of an element (also called its “stacking order”): the larger the value, the closer the element is to the observer. You can also think of it as a property affecting paint order, and this will in fact be more correct since the screen is a two-dimensional grid of pixels. So, the larger the z-index value, the later the element is painted on the page.
堆叠上下文的层次结构
如果你把网页想象成三维空间,则z-index是一个定义元素z坐标(也称为'堆叠顺序')的属性,属性值越大,元素距离观察者越近,你也可以把它想象成影响绘制顺序的属性,因为屏幕是一个二维像素网络,事实上,这是更正确的,因此,z-index的值越大,元素在页面上绘制的越晚。
There is, however, one major complication. The z-index value space is not flat — it’s hierarchical. An element can create a stacking context which becomes the root for z-index values of its descendants. It would be best to explain the concept of stacking contexts by using an example.
然而,这有一个主要的并发症,z-index属性值空间不是平的,它是分层的,元素可以创建一个层叠上下文,该上下文成为z-index其后代值的根,通过一个例子会更好去接受层叠上下文的概念。
The document body has five div descendants: div1, div2, div3, div1-1, and div2-1. They’re all absolutely positioned and overlap with each other. div1-1 is a child of div1, and div2-1 is a child of div2.
//css
div {
box-sizing: border-box;
border: 1px solid black;
padding: 5px;
position: absolute;
font-family: Verdana;
}
.div1, .div2, .div3 {
width: 500px;
height: 200px;
}
.div1-1, .div2-1 {
width: 200px;
height: 150px;
}
.div1 {
left: 10px;
top: 10px;
background-color: rgba(220, 220, 170, 0.9);
}
.div1-1 {
left: 250px;
top: 30px;
background-color: rgba(220, 170, 170, 0.9);
z-index: 10;
}
.div2 {
left: 20px;
top: 90px;
background-color: rgba(220, 170, 220, 0.9);
z-index: 1;
}
.div2-1 {
left: 120px;
top: 30px;
background-color: rgba(170, 220, 170, 0.9);
z-index: 10;
}
.div3 {
left: 30px;
top: 170px;
background-color: rgba(170, 220, 220, 0.9);
z-index: 2;
}
// html
<body>
<div class="div1">
<strong>div1</strong>
<br/>
(z-index: auto)
<div class="div1-1">
<strong>div1-1</strong>
<br/>
(z-index: 10)
</div>
</div>
<div class="div2">
<strong>div2</strong>
<br/>
(z-index: 1)
<div class="div2-1">
<strong>div2-1</strong>
<br/>
(z-index: 10)
</div>
</div>
<div class="div3">
<strong>div3</strong>
<br/>
(z-index: 2)
</div>
</body>
这个文档有5个div子代,div1, div2, div3, div1-1, and div2-1.它们都是绝对定位的并且彼此重叠, div1有一个子集div1-1, div2有一个子集div2-1
Let’s try to understand why we see what we see. There are quite elaborate rules to determine paint order, but here we only need to compare two things:
-
z-index
Values
If an element has a higherz-index
, it’s painted later. -
Source Order
Ifz-index
values are the same, then the later it’s in the source, the later it’s painted.
让我们试着理解为什么我们会看到我们所看到的,这是相当精细的 绘制顺序规则,但是在这里,我们只需要比较两件事。
z-index的值:z-index的值越高,绘制越晚
源序:如果z-index值相同,他的源绘制的越晚,它被绘制的时间就越晚
So if we don’t take stacking contexts into account, the order should be as follows:
因此,如果我们不考虑堆叠上下文,顺序应该如下:
div1
div2
div3
div1-1
div2-1
Note that div2-1 is in fact overlapped by div3. Why is that happening?
请注意,div2-1实际上与div3重叠,发生了什么?
If an element is said to create a stacking context, it creates a basis for its children’s z-index values, so they’re never compared with anything outside the stacking context when determining paint order. To put it another way, when an element creating a stacking context is painted, all its children are painted right after it and before any of its siblings.
如果一个元素创建了一个层叠上下文,它就为他的子集创建了一个z-index基数,所以在确定绘制顺序时,他们不会与层叠上下文外的元素比较,换种说法是,当创建的元素层叠上下文绘制的时候,在他的兄弟节点绘制之前,他所有的子集都会被正确绘制。
Going back to the example, the actual paint order of body’s descendant divs is:
再回去看这个例子,这个body后代div正确的绘制顺序是
div1
div2
div3
div1-1
Notice the absence of div2-1 in the list — it’s a child of div2 which creates a stacking context (because it’s an absolutely positioned element with a z-index other than the default value of auto), so it’s painted after div2, but before div3.
注意,这个列表中没有div2-1,他是div2的子集,div2已经创建了一个层叠上下文(因为,它是一个有z-index的绝对定位的元素,它的默认值不是auto)所以他在div2后,div3之前绘制
div1 doesn’t create a stacking context, because its implicit z-index value is auto, so div1-1 (its child) is painted after div2 and div3 (since its z-index, 10, is larger than that of div2 and div3).
div1 没有创建一个层叠上下文,因为它隐藏的z-index值时auto,所以div1-1(它的子集)在div2,和div3之后绘制(因为它的z-index10,大于div2和div3)
Don’t worry if you didn’t fully grasp this on first reading. There’s a bunch of online resources that do a great job in explaining these concepts in more detail:
- “The Stacking Context,” MDN web docs, Mozilla
- “What No One Told You About Z-Index,” Philip Walton
Note: It’s also great to be familiar with general paint order rules (which are actually quite complex).
如果你在第一次阅读时没有完全掌握这一点,不要担心。有一些在线资源可以更好地解释这些概念:
The main point of this piece, however, is how to deal with z-index
when your page is composed of dozens and hundreds of components, each potentially having children with z-index
defined.
然而,这篇文章的主要内容是当你的页面有成百上千组件组成,每个组件可能都有z-index定义的子项,你如何处理-z-index。
One of the most popular articles on z-index
proposes grouping all z-index
values in one place, but comparing those values doesn’t make sense if they don’t belong to the same stacking context (which might not be easy to achieve in a large application).
一个关于z-index很流行的文章建议将所有z-index值分组到一个位置,但是如果他们不属于同一个层叠上下文,比较他们就没有意义(在大型应用中,是不容易实现的)
Here’s an example. Let’s say we have a page with header and main sections. The main section for some reason has to have position: relative
and z-index: 1
.
// css
body {
margin: 0;
font-family: Verdana;
text-align: center;
background-color: white;
}
div {
display: flex;
align-items: center;
justify-content: center;
}
.header {
background-color: rgba(255, 255, 220, 0.5);
font-size: 10vmin;
position: relative;
height: 30vh;
}
.main {
background-color: rgba(220, 255, 255, 0.5);
font-size: 10vmin;
height: 70vh;
position: relative;
z-index: 1;
}
// html
<div class="header">
Header
</div>
<div class="main">
Main Content
</div>
这有一个例子,假设我们有一个带有标题和主要部分的页面,由于一些原因,main section 必须带有position: relative
and z-index: 1
We’re using a component architecture here, so CSS for the root component and every child component is defined in dedicated sections. In practice, components would live in separate files, and the markup would be generated using a JavaScript library of your choice, like React, but for demonstration purposes it’s fine to have everything together.
我们用这里用组件架构,所以根组件,和每一个子组件的css都是独立的,在实践中,组件是一个分开的文件,标记将使用你选择的JavaScript库生成,像react,但是为了演示的目的,把所有的东西都放在一起是可以的。
Now, imagine we’re tasked with creating a dropdown menu in the header. It has to be stacked on top of the main section, of course, so we’ll give it a z-index of 10:
现在,假设我们的任务是创建一个下拉菜单,当然,它必须放在主体的底部,所以我们给她设置一个z-index是10
// css
body {
margin: 0;
font-family: Verdana;
text-align: center;
background-color: white;
}
div {
display: flex;
align-items: center;
justify-content: center;
}
.header {
background-color: rgba(255, 255, 220, 0.8);
font-size: 10vmin;
position: relative;
height: 30vh;
}
.overlay {
position: absolute;
z-index: 10;
background-color: rgba(255, 220, 255, 0.8);
left: 10vw;
top: 25vh;
width: 50vw;
height: 50vh;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
}
.overlay::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 100%;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}
.main {
background-color: rgba(220, 255, 255, 0.8);
font-size: 10vmin;
height: 70vh;
position: relative;
z-index: 1;
}
// html
<div class="header">
Header
<div class="overlay">
Overlay
</div>
</div>
<div class="main">
Main Content
</div>
Now, a few months later, in order to make something unrelated work better, we apply the translateZ
hack to the header.
现在,过了一个月以后,为了让一些无关的东西更好地工作,我们将translatez-hack应用到头部。
As you can see, the layout is now broken. An element with z-index: 1
sits on top of an element with z-index: 10
, in the absence of any other z-index
rules. The reason is that the header now creates a stacking context — it’s an element with a transform
property whose value is anything other than none
(see full rules) and its own z-index
(0
by default) is lower than that of the main section (1
).
正如你看到的那样,布局已经损坏,在没有任何其他` z-索引规则的情况下,设置了z-index:1的元素在z-index:10的元素上,原因是header现在创建了一个块级上下文——一个具有transform属性,且属性值是none意外的其他值的元素,他自己的z-index比main section低。
The solution is straightforward: give the header a z-index
value of 2
, and it’ll be fixed.
解决方案很简单:给头部一个z-index
值2
,它将被修复。
The question is, how are we supposed to come to this solution if we have components within components within components, each having elements with different z-indices? How can we be sure that changing z-index of the header won’t break anything else?
问题是,如果组件中有组件,每个元素中都有不同的z-index,我们应该如何得到这个解决方案?如何确定,在改变头部的z-index情况下,不影响其他内容?
The answer is a convention that eliminates the need for guesswork, and it’s the following: changing z-indices within a component should only affect that component, and nothing else. To put it differently, when dealing with z-index values in a certain CSS file, we should ideally only concern ourselves with other values in that same file.
答案是个惯例,就是消除了猜测的必要性,如下:改变一个组件里z-index只影响这个组件,而不影响其他。换言之,当改变某个css文件里的z-index,理想情况下我们应该只关心同一个文件中的其他值。
Achieving it is easy. We should simply make sure that the root of every component creates a stacking context. The easiest way to do it is to give it position and z-index values other than the default ones (which are static and auto, respectively).
实现它很容易,我们应该确保每个根组件创建一个堆叠上下文,最简单的方式就是给他position和z-index默认值以为的值(分别是static和auto)
Here’s one of the ways to structure the application. It uses more elements than the previous one, but computation associated with extra DOM elements is cheap whereas developer’s time (a lot of which can sometimes be spent on debugging stacking issues) is definitely not.
这是构建应用程序的方法之一,它使用的元素比前一个元素多,但是与额外的dom元素相关的计算是便宜的,而开发人员的时间(其中很多时间有时可用于调试堆栈问题)绝对不是。
// css
body {
margin: 0;
font-family: Verdana;
text-align: center;
background-color: white;
}
/* Root styles */
.root__container {
position: relative;
z-index: 0;
}
.root__header {
position: relative;
z-index: 2;
}
.root__main {
position: relative;
z-index: 1;
}
/* Header styles */
.header__container {
background-color: rgba(255, 255, 220, 0.8);
font-size: 10vmin;
position: relative;
height: 30vh;
transform: translateZ(0);
display: flex;
align-items: center;
justify-content: center;
z-index: 0;
}
.header__overlay {
position: absolute;
z-index: 1;
background-color: rgba(255, 220, 255, 0.8);
left: 10vw;
top: 25vh;
width: 50vw;
height: 50vh;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
display: flex;
align-items: center;
justify-content: center;
}
.header__overlay::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 100%;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}
/* Main section styles */
.main__container {
background-color: rgba(220, 255, 255, 0.8);
font-size: 10vmin;
height: 70vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 0;
}
// html
<!-- root component starts -->
<div class="root__container">
<div class="root__header">
<!-- header component starts -->
<div class="header__container">
Header
<div class="header__overlay">
Overlay
</div>
</div>
<!-- header component ends -->
</div>
<div class="root__main">
<!-- main component starts -->
<div class="main__container">
Main Content
</div>
<!-- main component ends -->
</div>
</div>
<!-- root component ends -->
-
header__container
andmain__container
both haveposition: relative
andz-index: 0
; -
header__overlay
now hasz-index: 1
(we don’t need a large value since it’s only going to compete for stacking order with other elements within the header); -
root__header
now hasz-index: 2
, whereasroot__main
keeps itsz-index: 1
, and this is what makes the two siblings stack correctly.
(Note also that both have position: relative
since z-index
doesn’t apply to statically positioned elements.)
If we look at the header code now, we’ll notice that we can remove the z-index
property from both the container and the overlay altogether because the overlay is the only positioned element there. Likewise, the z-index
is not required on the main container. This is the biggest benefit of the proposed approach: when looking at z-indices, it’s only the component itself that matters, not its context.
如果我们现在看header中的代码,我们会注意到能够在容器中和覆盖中同时移除z-index属性,因为覆盖是其中唯一定位的元素。同样,z-index主容器上不需要,这是所提方法的最大好处:在查看z-indices时,只有组件本身才重要,而不是它的上下文。
Such an architecture is not without its drawbacks. It makes the application more predictable at the expense of some flexibility. For example, you won’t be able to create such overlays inside both the header and the main section:
这种架构也不是没有弊端,它以牺牲一些灵活性为代价使应用程序更具可预测性。例如,您将无法在标题和主要部分内创建此类叠加层:
body {
margin: 0;
font-family: Verdana;
text-align: center;
background-color: white;
}
div {
display: flex;
align-items: center;
justify-content: center;
}
.header {
background-color: rgba(255, 255, 220, 0.8);
font-size: 10vmin;
position: relative;
height: 30vh;
}
.header-overlay {
position: absolute;
z-index: 10;
background-color: rgba(255, 220, 255, 0.8);
left: 2vw;
top: 25vh;
width: 47vw;
height: 50vh;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
}
.header-overlay::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 100%;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}
.main {
background-color: rgba(220, 255, 255, 0.8);
font-size: 10vmin;
height: 70vh;
position: relative;
}
.main-overlay {
position: absolute;
z-index: 10;
background-color: rgba(255, 255, 255, 0.8);
left: 51vw;
bottom: 40vh;
width: 47vw;
height: 50vh;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
}
.main-overlay::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
top: 100%;
width: 0;
height: 0;
border-top: 10px solid rgba(255, 255, 255, 0.8);
border-right: 10px solid transparent;
border-left: 10px solid transparent;
border-bottom: 10px solid transparent;
}
// <div class="header">
Header
<div class="header-overlay">
Header Overlay
</div>
</div>
<div class="main">
Main Content
<div class="main-overlay">
Main Overlay
</div>
</div>
In my experience, however, this is rarely a problem. You could make the overlay in the main section go down instead of up, in order for it to not intersect with the header. Or, if you really needed it to go up, you could inject the overlay HTML at the end of the body and give it a large z-index (“large” being whatever’s larger than those of other sections at the top level). In any case, if you’re not in a competition to build the most complicated layout, you should be fine.
根据我的经验,这很少是一个问题,您可以使主要部分中的叠加层向下而不是向上,以使其不与标题相交。或者如果 你真的需要它在上面,你可以在正文的末尾注入叠加HTML并给他设置一个大的z-index,无论如何,如果你不是在竞争中构建最复杂的布局,你应该没问题。
To recap:
Isolate components in terms of z-index values of elements by making the root of each component a stacking context;
You don’t have to do it if no element within a component needs a z-index value other than auto;
Within a component’s CSS file, maintain z-index values any way you like. It might be consecutive values, or you could give them a step of 10, or you can use variables — it all depends on your project’s conventions and the size of the component (although making components smaller is never a bad thing). Preferably, only assign z-index to sibling elements. Otherwise, you may inadvertently introduce more stacking contexts within a component, and you’re faced with the same issue again, luckily on a smaller scale;
Debugging becomes easy. Find the first ancestor component of the two elements that are not stacked correctly, and change z-indices within that component as necessary.
This approach will hopefully bring back some sanity to your development process.
回顾一下:
*z-index通过使每个组件的根成为堆叠上下文,根据元素的值来隔离组件 ;
*如果组件中的元素不需要z-index除以外的值,则不必执行此操作auto;
*在组件的css文件中,以你喜欢的方式维护z-index,它可能是连续的值,也可以从10 起步,或者你可以使用一个变量——这都取决于你的项目约定和组件的大小(尽管组件变小从来都不是坏事),更好的是,仅分配z-index给兄弟元素。否则,您可能会无意中在组件中引入更多堆叠上下文,并且您再次面临同样的问题,幸运的是规模较小;
- 调试变的容易,找到未正确堆叠的两个元素的第一个祖先组件,并根据需要更改该组件中的z-indices
希望这些方法能够给你的开发过程带来一些帮助。
翻译过程中查询的单词
despite:尽管
mishandled:处理不当
Stacking:堆叠
Adhering:坚持
thousands:数千
recap:扼要重述
actually:(在口语中用于强调事实)的确,真实地,事实上;(表示想法与事实不一致因而惊奇)居然,竟然;(礼貌地纠正他人)实际上
convenient:方便的
coordinate:坐标
correct:正确的,合适的
two-dimensional grid of pixels:二维像素网络
complication:并发症
hierarchical:按等级划分的;等级制度的
descendants:后代
overlap:重叠
elaborate : 详细的,精心制作的
determining:查明;测定;准确算出;决定;形成;支配;影响;确定;裁决;安排
demonstration:示范
purposes:目的
convention:惯例
eliminates:消除
guesswork:猜想
associated:相关的
drawbacks:弊端
Isolate:分离物
maintain:坚持,保持
assign:分配