CSS解析:定位和层叠上下文
许多开发人员对定位的理解很粗略,如果不完全了解定位,就很容易给自己挖坑。有时候可能会把错误的元素放在其他元素前面,要解决这个问题却没有那么简单。
一般的布局方法是用各种操作来控制文档流的行为。定位则不同:它将元素彻底从文档流中移走。它允许你将元素放在屏幕的任意位置。还可以将一个元素放在另一个元素的前面或后面,彼此重叠。
默认定位
<style>
div {
/* 初始值静态定位static,未被定位 */
postion:static;
}
</style>
使用静态定位,称之为未被定位。
改成其他值后,说明元素被定位了。
定位的元素从文档流中移走了,意味着允许将元素放在屏幕任意位置,还可以放在另一个元素的前面或后面彼此重叠。
一、固定定位
给一个元素设置position: fixed
就能将元素放在视口的任意位置。这需要搭配四种属性一起使用:top、right、bottom和left
。这些属性的值决定了固定定位的元素与浏览器视口边缘的距离。
比如,top: 3em表示元素的上边缘距离视口顶部3em。
<style>
div {
// 可以将元素放在视口任意位置
postion:fixed;
//通过四个值控制元素与浏览器视口边缘的位置
//同时四个值隐式制定元素的大小
top:0;
left:0;
right:0;
bottom:0;
}
</style>
(一)用固定定位创建模态框
我们要用这些属性创建一个如图所示的模态框。该模态框会在网页内容前弹出来,它会挡住网页内容,直到关闭该弹窗。
通常情况下,模态框用于要求用户阅读一些内容或者在下一步操作之前输入一些内容。比如,如图的模态框展示了一个表单,用户可以注册一个时事通讯。初始状态下用display:none隐藏弹窗,然后用JavaScript将display改成block以显示弹窗。
<body>
<header class="top-banner">
<div class="top-banner-inner">
<p>前往注册账号 <button id="open">注册</button></p>
</div>
</header>
<div id="modal" class="modal">
<div class="modal-backdrop"></div>
<div class="modal-body">
<button class="modal-close" id="close">close</button>
<h2>标题栏</h2>
<p>请注册电子邮件!</p>
<form>
<p>
<label for="email">email:</label>
<input type="text" name="email" />
</p>
<p>
<button type="submit">提交</button>
</p>
</form>
</div>
</div>
</body>
<script type="text/javascript">
var button = document.getElementById('open')
var close = document.getElementById('close')
var modal = document.getElementById('modal')
button.addEventListener('click', function (e) {
e.preventDefault()
modal.style.display = 'block'
})
close.addEventListener('click', function (e) {
e.preventDefault()
modal.style.display = 'none'
})
</script>
代码里的第一个元素是顶部条。它包含了触发模态框的按钮。第二个元素是模态框。它包括一个空的modal-backdrop,用来遮住页面剩余部分,将用户的注意力集中到弹窗的内容。弹窗内容在modal-body里。
body {
font-family: Helvetica, Arial, sans-serif;
/* 设置网页高度,让页面出现滚动条(只是为了演示) */
min-height: 200vh;
margin: 0;
}
button {
padding: 0.5em 0.7em;
border: 1px solid #8d8d8d;
background-color: white;
font-size: lem;
}
.top-banner {
padding: lem 0;
background-color: #ffd698;
}
.top-banner-inner {
width: 80%;
max-width: 1000px;
margin: 0 auto;
}
.modal {
/*默认隐藏模态框。当要打开模态框的时候,JavaScript会设置display: block*/
display: none;
}
.modal-backdrop {
/*当打开模态框时,用半透明的蒙层遮挡网页剩余内容*/
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-body {
/*给模态框的主体定位*/
position: fixed;
top: 3em;
bottom: 3em;
right: 20%;
left: 20%;
padding: 2em 3em;
background-color: white;
/*允许模态框主体在需要时滚动*/
overflow: auto;
}
.modal-close {
cursor: pointer;
}
在这段CSS里,我们使用了两次固定定位。第一次是modal-backdrop
的蒙层,四个方向都设置为0。
这让蒙层填满整个视口。它还有一个背景色rgba(0, 0, 0, 0.5)。这个颜色符号指定了红、绿、蓝的值均为0,算出来是黑色。第四个值是“alpha”通道,它指定透明度:0是完全透明,1是完全不透明。0.5是半透明,因此该元素下面所有的网页内容就会变暗。
第二次固定定位了modal-body
。它的四条边都在视口内:顶边和底边到视口对应的边缘为3em,左边和右边距离视口对应的边缘为20%。因为它的背景色为白色,所以模态框呈现为一个在屏幕居中的白色盒子。
虽然可以随意滚动网页,但是背景和模态框主体都不会动。打开页面,我们看到屏幕上方有一个带按钮的淡黄色顶部条。点击按钮打开定位的模态框。
因为是固定定位,所以即使滚动页面,模态框的位置也不会变(为了演示,我们特地将body上的min-height值设得很大,撑出了滚动条)。点击模态框顶部的Close按钮,关闭弹窗。这个按钮现在的位置不太对,我们稍后会调整它的位置。
(二)控制定位元素的大小
定位一个元素时,不要求指定四个方向的值,可以只指定需要的方向值,然后用width和/或height来决定它的大小,也可以让元素本身来决定大小。请看如下声明。
position:fixed;
top:lem;
right:1em;
width:20%;
这段代码会将元素放在距离视口顶部和右边1em的位置,宽度为视口宽度的20%。它省略bottom和height属性,元素的高度由自身的内容决定。
例如,这可以用于将一个导航菜单固定到屏幕上。即使用户滚动网页内容,该元素的位置也不会改变。因为固定元素从文档流中移除了,所以它不再影响页面其他元素的位置。
别的元素会跟随正常文档流,就像固定元素不存在一样。也就是说它们通常会在固定元素下面排列,视觉上被遮挡。这对于模态框来说没问题,因为我们希望模态框出现在最前面的中间位置,直到用户关闭它。
而对于其他固定元素,比如侧边导航栏,就需要注意不要让其他内容出现在它下面。通常给其他内容加一个外边距就能解决该问题。比如,将所有内容放在容器里,容器设置right-margin:20%。外边距会流到固定元素下面,内容就不会跟导航栏重叠。
二、绝对定位
固定定位让元素相对视口定位,此时视口被称作元素的包含块(containing block)。
绝对定位的行为也是如此,只是它的包含块不一样。绝对定位不是相对视口,而是相对最近的祖先定位元素。
跟固定元素一样,属性top、right、bottom和left决定了元素的边缘在包含块里的位置。
<style>
div {
// 可以将元素放在祖先定位元素的任意位置
postion:absolute;
//同样,通过四个值控制元素与浏览器视口边缘的位置
top:0;
left:0;
right:0;
bottom:0;
}
(一)让Close按钮绝对定位
为了演示绝对定位,我们重新设置Close按钮的位置,将其放在模态框的右上角,如图所示。
我们需要将Close按钮设置为绝对定位。因为它的父元素modal-body是固定定位的,所以会成为Close按钮的包含块。
.modal-close {
position:absolute;
top:0.3em;
right:0.3em;
padding:0.3em;
cursor:pointer
}
这段代码将按钮放在距离modal-body顶部0.3em、右侧0.3em的位置。通常情况下,就像本例一样,包含块是元素的父元素。如果父元素未被定位,那么浏览器会沿着DOM树往上找它的祖父、曾祖父,直到找到一个定位元素,用它作为包含块。
如果祖先元素都没有定位,那么绝对定位的元素会基于初始包含块(initialcontaining block)来定位。初始包含块跟视口一样大,固定在网页的顶部。
(二)定位伪元素
Close按钮已经定位好了,只是过于简陋。对于这种Close按钮,用户通常期望看到一个类似于x的图形化显示,如图所示。
你可能首先想到将按钮里的文字close换成x,但是这会导致可访问性的问题:辅助的屏幕阅读器会读按钮里的文字。因此要给这个按钮一些有意义的提示。在使用CSS之前,HTML本身必须有意义。
相反,你可以用CSS隐藏close,并显示x。总共需要两步。首先将按钮的文字挤到外面,并隐藏溢出内容。然后将按钮的::after伪元素的content属性设置为x,并让伪元素绝对定位到按钮中间。
.modal-close {
position: absolute;
top: 0.3em;
right: 0.3em;
padding: 0.3em;
cursor: pointer;
font-size: 2em;
/*让按钮变成方形*/
height: 1em;
width: 1em;
/*让元素里的文字溢出并隐藏*/
text-indent: 10em;
overflow: hidden;
border: 0;
}
.modal-close::after {
position: absolute;
line-height: 0.5;
top: 0.2em;
left: 0.1em;
text-indent: 0;
/* 添加 Unicode 字符 U+00D7(乘法符号)*/
content: '\00D7';
}
以上代码清单明确指定按钮为1em大小的方形。text-indent属性将文字推到右边,溢出元素它的确切值不重要,只要大于按钮宽度即可。
由于text-indent是继承属性,需要在伪类元素选择器上设为0,因此x便不会缩进。伪类元素现在是绝对定位。因为它表现得像按钮的子元素一样,所以定位的按钮就成为其伪元素的包含块。设置一个较小的line-height让伪元素不要太高,用top和left属性让它在按钮中间定位。
绝对定位是定位类型里的重量级选手。它经常跟JavaScript配合,用于弹出菜单、工具提示以及消息盒子。我们将用绝对定位来构建一个下拉菜单,但在此之前,我们需要先看看它的搭档:相对定位。
三、相对定位
相对定位可能是最不被理解的定位类型。当第一次给元素加上position: relative
的时候,你通常看不到页面上有任何视觉改变。相对定位的元素以及它周围的所有元素,都还保持着原来的位置。
如果加上top、right、bottom和left属性,元素就会从原来的位置移走,但是不会改变它周围任何元素的位置
<style>
div {
postion:relative;
top:1em;
left:2em;
}
如图所示,四个inline-block元素,给第三个元素加上三个额外的属性:position: relative、top: 1em、left: 2em
,将其从初始位置移走,但是其他元素没有受到影响。它们还是围绕着被移走元素的初始位置,跟随着正常的文档流。
设置top: 1em将元素从原来的顶部边缘向下移动了1em;设置left: 2em将元素从它来的左侧边缘向右移动了2em。这可能导致元素跟它下面或者旁边的元素重叠。在定位中,也可以使用负值,比如bottom: -1em也可以像top: 1em那样将元素向下移动1em。
跟固定或者绝对定位不一样,不能用top、right、bottom和left改变相对定位元素的大小。这些值只能让元素在上、下、左、右方向移动。可以用top或者bottom,但它们不能一起用(bottom会被忽略)。同理,可以用left或right,但它们也不能一起用(right会被忽略)。
有时可以用这些属性调整相对元素的位置,把它挤到某个位置,但这只是相对定位的一个冷门用法。更常见的用法是使用position: relative
给它里面的绝对定位元素创建一个包含块。
实际应用:
由于绝对定位的最近父元素必须是定位元素,所以将父元素设置为相对定位。既不影响父元素,又可以实现绝对定位。
(一)创建一个下拉菜单
接下来我们用相对和绝对定位创建一个下拉菜单。它的初始状态是一个简单的矩形,当用户鼠标悬停到上面时,会弹出一个链接列表。
<div class="container">
<naV>
<div class="dropdown">
<div class="dropdown-label">下拉菜单</div>
<div class="dropdown-menu">
<ul class="submenu">
<li>首页</li>
<li>新闻中心</li>
<li>服务支持</li>
<li>关于我们</li>
</ul>
</div>
</div>
</nav>
<h1>这是一个h1标题</h1>
</div>
将它添加到HTML中,放在<div class="modal">
的结束标签后面。这段代码包含了一个容器元素,之后我们会将它的内容居中,并让它跟顶部条的内容对齐。我还在弹出列表下面放了一个<h1>
标签,以展示弹出列表如何出现在其他网页内容前面。
下拉菜单容器包含两个子元素:一个始终显示的灰色矩形标签以及一个下拉菜单。下拉菜单用显示和隐藏表示菜单展开和收起。因为它会是绝对定位的,所以当下拉菜单显示时不会改变网页的布局,这意味着它显示时会出现在其他内容前面。
.container {
width: 80%;
max-width: 1000px;
margin: 1em auto;
}
.dropdown {
display: inline-block;
position: relative;
}
.dropdown-label {
padding: .5em 1.5em;
border: 1px solid #ccc;
background-color: #eee;
}
.dropdown-menu {
display: none;
position: absolute;
left: 0;
top: 2.1em;
min-width: 100%;
background-color: #eee;
}
.dropdown:hover .dropdown-menu {
display: block;
}
.submenu {
padding-left: 0;
margin: 0;
list-style-type: none;
border: 1px solid #999;
}
.submenu > li + li {
border-top: 1px solid #999;
}
.submenu > li >a {
display: block;
padding: .5em 1.5em;
background-color: #eee;
color: #369;
text-decoration: none;
}
.submenu > li > a:hover {
background-color: #fff;
}
当移动鼠标指针到主菜单标签时,下拉菜单就会从下面弹出。请注意,这里是在整个容器上设置:hover状态来打开菜单。也就是说只要鼠标停在它的任何内容上,无论是dropdown-label还是dropdown-menu,菜单都会保持打开状态。
绝对定位的dropdown-menu设置了left: 0,让其左边和整个容器的左侧对齐。然后它使用top: 2.1em将其顶部边缘放在标签下面(算上内边距和边框,标签高2.1em)。
min-width为100%,保证它至少等于容器的宽度(容器宽度由dropdown-label决定)。之后用submenu类给下来菜单内的菜单加上样式。
(二)创建CSS三角形
下拉菜单距离完美还差一步。现在它已能正常工作,但用户无法一眼察觉到主菜单标签下面还有更多内容。我们来给标签加上一个小的向下箭头,告诉用户还有更多内容。
我们可以用边框画一个三角形当作向下箭头。这里用标签的::after伪元素来画三角形,然后使用绝对定位将它放到标签的右边。大多数情况下,我们会给一个元素加上较细的边框,通常1px或者2px就够了,但如果把边框变得像图那样粗呢?图中给每条边都加了独特的颜色,用来标出每条边的起始位置。
注意观察角上两条边的边缘接触的地方:它们形成了一个对角边。再观察一下将元素的宽和高缩小到0时会发生什么。所有的边都汇聚到一起最后在中间连接起来了。
元素四周的边都变成了三角形。顶部的边箭头指向下边,右边的边指向左边,以此类推。基于这个现象,可以用一条边作为三角形,然后将剩下的边设置为透明。元素的左右边都透明,而顶部边可见,就会如图所示,形成一个简单的三角形。
我们给dropdown-label::after伪元素加上样式,做一个三角形,并让它绝对定位。
.dropdown-label {
/*增加右侧内边距给箭头留位置*/
padding: .5em 2em .5em 1.5em;
border: 1px solid #ccc;
background-color: #eee;
}
.dropdown-label::after {
content: "";
position: absolute;
right: 1em;
/*在标签的右边定位元素*/
top: 1em;
border: 0.3em solid;
border-color: black transparent transparent;
}
.dropdown:hover .dropdown-label::after {
top: 0.7em;
border-color: transparent transparent black;
}
伪元素因为没有内容,所以没有宽或高。然后用border-color简写属性设置上边框为黑色,左右和下面的边框为透明,构造一个向下的箭头。dropdown-label右边用内边距留出了空间,用来放三角形。最后的效果如图所示。
打开菜单,箭头方向反转,朝向上面,表示菜单可以被关闭。微调top值(从1em到0.7em),让向上的箭头看起来跟向下的箭头处于相同的位置。
另外你也可以用一个图片或者背景图来实现箭头,但是用短短几行CSS代码就可以为用户免去不必要的网络请求。加上这个小小的箭头,能给网站或应用程序增色不少。
这项技术还可以用来构建其他复杂形状,比如梯形、六边形和星形。查看用CSS构建的各种形状,可以访问css-tricks网站上的文章The Shapes of CSS。
四、层叠上下文
定位非常有用,但也需要弄清楚它会带来什么后果。当把一个元素从文档流中移除时,我们就需要管理之前由文档流处理的所有事情了。
首先要确保元素不会不小心跑到浏览器视口之外,导致用户会看不到元素。
其次要保证元素不会不小心挡住重要内容。
最后还有层叠的问题。在同一页面定位多个元素时,可能会遇到两个不同定位的元素重叠的现象。有时我们会发现“错误”的元素出现在其他元素之前。
(一)层叠顺序
浏览器将HTML解析为DOM的同时还创建了另一个树形结构,叫作渲染树(render tree)。它代表了每个元素的视觉样式和位置。同时还决定浏览器绘制元素的顺序。
顺序很重要,因为如果元素刚好重叠,后绘制的元素就会出现在先绘制的元素前面。通常情况下(使用定位之前),元素在HTML里出现的顺序决定了绘制的顺序。考虑以下代码里的三个元素:
<div>one</div>
<div>two</div>
<div>three</div>
它们的层叠行为如图所示。这里使用了负的外边距让元素重叠,但并未使用任何定位。后出现在标记里的元素会绘制在先出现的元素前面。
定位元素时,这种行为会改变。浏览器会先绘制所有非定位的元素,然后绘制定位元素。默认情况下,所有的定位元素会出现在非定位元素前面。如图所示,给前两个元素加了position: relative
,它们就绘制到了前面,覆盖了静态定位的第三个元素,尽管元素在HTML里的顺序并未改变。
注意,在定位元素里,第二个定位元素还是出现在第一个定位元素前面。定位元素会被放到前面,但是基于源码的层叠关系并没有改变。
也就是说在上述网页里,模态框和下拉菜单都会出现在静态内容之前(符合预期),但是源码里后出现的元素会绘制在先出现的元素之前。
解决这个问题的一个办法是在源码里将<div class="modal">
及其内容移到下拉菜单后面。通常情况下,模态框要放在网页内容的最后,</body>
关闭标签之前。大多数构建模态框的JavaScript库会自动这样做。因为模态框使用固定定位,所以不必关心它的标记出现在哪里,它会一直定位到屏幕中间。
改变固定定位元素的标记位置不会产生不好的影响,但是对相对定位或绝对定位的元素来说,通常无法用改变标记位置的方法解决层叠问题。相对定位依赖于文档流,绝对定位元素依赖于它的定位祖先节点。这时候需要用z-index属性来控制它们的层叠行为。
(二)使用Z-INDEX控制层叠顺序
z-index属性的值可以是任意整数(正负都行)。z表示的是笛卡儿x-y-z坐标系里的深度方向。拥有较高z-index的元素出现在拥有较低z-index的元素前面。拥有负数z-index的元素出现在静态元素后面。
使用z-index是解决网页层叠问题的第二个方法。该方法不要求修改HTML的结构。将modal-backdrop的z-index设置为1,将modal-body的z-index设置为2(确保模态框的主体在蒙层前面)。
.modal-backdrop {
/*当打开模态框时,用半透明的蒙层遮挡网页剩余内容*/
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.modal-body {
/*给模态框的主体定位*/
position: fixed;
top: 3em;
bottom: 3em;
right: 20%;
left: 20%;
padding: 2em 3em;
background-color: white;
/*允许模态框主体在需要时滚动*/
overflow: auto;
z-index: 2;
}
z-index的行为很好理解,但是使用它时要注意两个小陷阱。
第一,z-index只在定位元素上生效,不能用它控制静态元素。
第二,给一个定位元素加上z-index可以创建层叠上下文。
(三)层叠上下文
一个层叠上下文包含一个元素或者由浏览器一起绘制的一组元素。其中一个元素会作为层叠上下文的根,比如给一个定位元素加上z-index的时候,它就变成了一个新的层叠上下文的根。所有后代元素就是这个层叠上下文的一部分。
实际上将层叠上下文里的所有元素一起绘制会造成严重的后果:层叠上下文之外的元素无法叠放在层叠上下文内的两个元素之间。
换句话说,如果一个元素叠放在一个层叠上下文前面,那么层叠上下文里没有元素可以被拉到该元素前面。同理,如果一个元素被放在层叠上下文后面,层叠上下文里没有元素能出现在该元素后面。
<div class="box one positioned">
one
<div class="absolute">nested</div>
</div>
<div class="box two positioned">two</div>
<div class="box three">three</div>
这段代码包含了三个盒子,其中两个被定位,并且z-index为1,第一个盒子里面有一个绝对定位的元素,它的z-index为100。
body {
margin: 40px;
}
.box {
display: inline-block;
width: 200px;
line-height: 200px;
text-align: center;
border: 2px solid black;
background-color: #ea5;
margin-left: -60px;
vertical-align: top;
}
.one {
margin-left: 0;
}
.two {
margin-top: 30px;
}
.three {
margin-top: 60px;
}
.positioned {
position: relative;
background-color: #5ae;
z-index: 1;
}
.absolute {
position: absolute;
top: 1em;
right: 1em;
height: 2em;
background-color: #fff;
border: 2px dashed #888;
z-index: 100;
line-height: initial;
padding: 1em;
}
虽然第一个盒子里绝对定位的子元素nested的z-index很高,但还是出现在第二个盒子后面,因为它的父元素,即第一个盒子形成的层叠上下文在第二个盒子后面
叠放在第二个盒子后面的第一个盒子是一个层叠上下文的根。因此,虽然nested所在div的z-index值很高,但是它内部的绝对定位元素不会跑到第二个盒子前面。在浏览器开发者工具里试验一下,感受这种关系,改变每个元素的z-index看看会发生什么。
给一个定位元素加上z-index是创建层叠上下文最主要的方式,但还有别的属性也能创建,比如小于1的opacity属性,还有transform、filter属性。由于这些属性主要会影响元素及其子元素渲染的方式,因此一起绘制父子元素。文档根节点(
<html>
)也会给整个页面创建一个顶级的层叠上下文。
所有层叠上下文内的元素会按照以下顺序,从后到前叠放:
❑ 层叠上下文的根
❑ z-index为负的定位元素(及其子元素)
❑ 非定位元素
❑ z-index为auto的定位元素(及其子元素)
❑ z-index为正的定位元素(及其子元素)
如果不根据组件的优先级定义清晰的层叠顺序,那么一个样式表很容易演变成一场z-index大战。如果没有清晰的说明,开发人员在给一个模态框之类的元素添加样式时,为了不被其他元素遮挡,就会设置一个高得离谱的z-index,比如999999。
这样的事情重复几次后,大家就只能凭感觉给一个新的组件设置z-index。如果你使用预处理器,比如LESS或SASS,或者你支持的所有浏览器都支持自定义属性,就能很方便地处理这个问题。将所有的z-index都定义为变量放到同一个地方,如下代码片段所示。这样就能清晰地看到哪些元素在前哪些元素在后。
--z-loading-indicator:100;
--2-nav-menu:200;
--z-dropdown-menu:400;
--z-modal-backdrop:300;
--z-modal-body:410;
将增量设为10或者100,这样就能在需要的时候往中间插入新值。
如果发现z-index没有按照预期表现,就在DOM树里往上找到元素的祖先节点,直到发现层叠上下文的根。然后给它设置z-idnex,将整个层叠上下文向前或者向后放。还要注意多个层叠上下文嵌套的情况。
网页很复杂时,很难判断是哪个层叠上下文导致的问题。因此,在创建层叠上下文的时候就一定要多加小心,没有特殊理由的话不要随意创建,尤其是当一个元素包含了网页很大一部分内容的时候。
尽可能将独立的定位元素(比如模态框)放到DOM的顶层,结束标签</body>
之前,这样就没有外部的层叠上下文能束缚它们了。
有些开发人员会忍不住给页面的大量元素使用定位。一定要克制这种冲动。定位用得越多,网页就越复杂,也就越难调试。如果你定位了大量元素,就回头评估一下现在的情况,尤其是当你发现很难调试出自己想要的布局时,一定要反思。
如果可以用别的方法实现某个布局,应该优先用那些方法。如果能够依靠文档流,而不是靠明确指定定位的方式实现布局,那么浏览器会帮我们处理好很多边缘情况。记住,定位会将元素拉出文档流。
一般来说,只有在需要将元素叠放到别的元素之前时,才应该用定位。
五、粘性定位
人们已经用四种主要的定位类型(静态、固定、绝对以及相对)很长时间了,不过现在浏览器还提供了一种新的定位类型:粘性定位(sticky positioning)。
它是相对定位和固定定位的结合体:正常情况下,元素会随着页面滚动,当到达屏幕的特定位置时,如果用户继续滚动,它就会“锁定”在这个位置。最常见的用例是侧边栏导航。
网页刚加载的时候,侧边栏的位置一切正常。网页滚动,它也跟着滚动直到滚到快要离开视口的时候,它会锁定在那个位置。当网页的剩余部分继续滚动时,它却好像固定定位的元素一样停留在屏幕上。
接下来修改网页结构,定义两栏。在HTML里将容器改成如代码所示的代码。把之前的内容(下拉菜单和网页标题)放在左边栏,再添加一个右边栏放“affix”菜单。
<div class="container">
<main class="col-main">
<nav>
<div class="dropdown">
<div class="dropdown-label">下拉菜单</div>
<div class="dropdown-menu">
<ul class="submenu">
<li>首页</li>
<li>新闻中心</li>
<li>服务支持</li>
<li>关于我们</li>
</ul>
</div>
</div>
</nav>
<h1>这是一个h1标题</h1>
</main>
<aside class="col-sidebar">
<div class="affix">
<ul class="submenu">
<li>首页</li>
<li>新闻中心</li>
<li>服务支持</li>
<li>关于我们</li>
</ul>
</div>
</aside>
</div>
接下来更新CSS,将容器设为弹性容器,设置两栏的宽度。本例复用了下拉菜单的子菜单的样式,当然你也可以给侧边栏添加其他的元素和样式。
.container {
display: flex;
width: 80%;
max-width: 1000px;
margin: lem auto;
min-height: 100vh;
}
.col-main {
flex: 1 80%;
}
.col-sidebar {
flex: 20%;
}
.affix {
position: sticky;
top: 1em;
}
以上代码主要用来设置两栏布局。最后只用了两句声明来给affix元素定位。
top值设置了元素最终固定的位置:距离视口的顶部1em。因为粘性元素永远不会超出父元素的范围,所以本例中affix不会超出col-sidebar的范围。
当滚动页面的时候,col-sidebar会一直正常滚动,但是affix会在滚动到特定位置时停下来。如果继续滚动得足够远,粘性元素还会恢复滚动。这种情况只在父元素的底边到达粘性元素的底边时发生。注意,只有当父元素的高度大于粘性元素时才会让粘性元素固定,因此这里特意给弹性容器加上min-height,以便让父元素足够高。
.sticky {
//粘性定位元素的表现与其父元素有关。
position: sticky;
//必须指定 top、right、bottom、left 四个阈值的其中之一,粘性定位才会生效
top: 10px;
}
使用粘性定位时要注意浏览器的兼容性。