CSS 定位布局:让元素各就各位

5 阅读12分钟

前言

写网页很像安排一场舞台演出:有人按照队伍依次登场,有人需要从原位挪开一点,有人要直接站到舞台中央,还有人无论页面怎样滚动,都要守在屏幕边缘。

CSS 定位布局解决的就是“元素应该出现在哪里”这个问题。要真正理解它,不必急着背属性,而要先弄清三个关键点:元素原本怎样排列、定位时以谁为参照、移动后是否还占据原来的空间。

一、文档流

浏览器在没有特殊布局规则时,页面布局默认是按从上往下,从左往右的排列,呈现一种流式的排列,并且会按照 HTML 元素出现的先后顺序排列内容。这种默认的排列秩序叫作文档流

可以把页面想象成一条井然有序的队伍:

  • 块级元素通常从上到下排列,一个接一个;
  • 行内内容通常从左到右排列,一行放不下时再换到下一行;
  • 前面的元素占据多少空间,会直接影响后面元素的位置。

文档流的价值在于,它让页面具有自然的伸缩能力。文字变多时,容器会被撑开;某个模块变高时,后面的内容会自动向下移动。开发者不需要为每个元素计算精确坐标,浏览器就能完成大部分基础排版。

什么是脱离文档流

如果一个元素脱离文档流,其他元素在排版时就会忽略它原本占据的空间。

这就像一个人离开了正在排队的队伍:他仍然可能站在大家看得见的地方,但后面的人已经向前补位,不会继续为他留出位置。因此,脱离文档流的元素很容易与其他内容发生重叠。

判断一种布局方式时,可以先问两个问题:

  1. 元素移动后,原来的位置是否还被保留?
  2. 后面的元素是否仍然需要为它让出空间?

只要把这两个问题想清楚,后面的定位概念就会容易很多。

1. 块级元素:一个人占一排

块级元素通常会独占一行。即使它本身没有占满整行,后面的块级元素一般也会从下一行开始排列。

它的主要特点包括:

  • 通常从上到下排列;
  • 可以直接设置宽度和高度;
  • 没有指定宽度时,通常会尽可能占满父容器的可用宽度;
  • 很适合构建页面中的大块结构。

常见的块级标签有 divph1。它们默认会从新的一行开始,适合搭建页面结构或承载大块内容。

可以把块级元素想成一位包下整排座位的观众:他自己可能只坐在其中一小块区域,但下一位观众仍然要去下一排。

2. 行内元素:跟着文字一起走

行内元素不会主动独占一行,而是像句子中的文字一样,从左到右连续排列。只有当前行空间不足时,它们才会自然换行。

它的主要特点包括:

  • 可以与其他行内内容出现在同一行;
  • 占据的空间通常由自身内容决定;
  • 普通情况下,不能像块级元素那样直接控制宽度和高度;
  • 很适合表示文字中的局部内容。

常见的行内标签有 spanastrong。它们适合嵌入句子内部,不会因为自身出现就强制换行。

行内元素就像坐在同一排长椅上的人:每个人只占自己需要的位置,只要长椅还有空间,后来的人就可以继续坐在旁边。

3. 行内块元素:既能同排,又能控制尺寸

行内块元素结合了前两种类型的部分特点。它在排列方式上像行内元素,可以和其他元素待在同一行;在尺寸控制上又像块级元素,可以设置明确的宽度和高度。

它的主要特点包括:

  • 不会默认独占一整行;
  • 可以控制宽度和高度;
  • 可以在同一行放置多个尺寸明确的元素;
  • 适合制作按钮、标签和横向导航项。

buttoninputselect 等表单控件通常具有类似行内块元素的表现,既能在同一行排列,也可以控制尺寸。

如果说块级元素是“一人一排”,行内元素是“大家挤在长椅上”,那么行内块元素就像一排尺寸固定的独立座椅:每个座位大小明确,又能整齐地排在同一行。

三种类型可以这样对比:

元素类型是否通常独占一行能否直接控制宽高
块级元素可以
行内元素通常不可以
行内块元素可以

需要注意的是,元素类型主要决定它在文档流中如何排列;定位布局则进一步决定它能否偏离原位置、以谁为参照以及是否脱离文档流。两者相互配合,但不是同一个概念。

标签的默认显示类型也可以通过 CSS 中的 display 改变,如:

display: block;         //表示设置为块级元素
display: inline;        //表示设置为行内元素
display: inline-block;  //表示设置为行内块元素

因此应以元素当前的显示方式判断它如何参与布局。

二、四大定位布局类型

CSS 中常见的定位方式包括相对定位、绝对定位、固定定位和黏性定位。理解它们时,不要只记名称,而要同时观察三个维度:参照物、文档流和使用场景

1. 相对定位:从自己的座位挪一下

position: relative;相对定位以元素原本在文档流中的位置作为参照。元素可以从原位置向上、向下、向左或向右偏移。以下面的HTML代码为例:

<style>
        *{
            margin: 0;
        }
        .box1{
            width: 100px;
            height: 100px;
            background-color: #e237cb;
            position: relative;  //设置为相对布局
            /* 相对在原本的文档流的位置进行偏移 */
            left: 200px;   // 向右偏移200px
            top: 100px;    // 向下偏移100px
        }
        .box2{
            width: 200px;
            height: 200px;
            background-color: #252577;
        }
 </style>

<body>
    <div class="box1"></div>
    <div class="box2"></div>
</body>

image.png

我们已知 box1box2 为都为div块级元素,应占据一整行,然后我们将box1设置为定位布局后, box1所代表的紫色色块分别向右和下进行的移动,但也可以看到,其所原本占据的文档流位置仍存在。

这也是定位布局最鲜明的特点:元素虽然在视觉上移动了,但原来占据的空间仍然保留。后面的元素依旧按照它移动前的位置进行排列。即相对定位不会导致脱离文档流。

这就像一个人从自己的座位旁边站了起来:别人能看到他的位置发生了变化,但他的座位仍然被占着,其他人不能坐进去。

相对定位通常有两类用途:

  • 对元素进行小范围的位置微调;
  • 为内部的绝对定位元素建立一个明确的参照范围。

第二种用途尤其常见,我将在“绝对定位这部分”举例。很多时候,父元素开启相对定位并不是为了移动自己,而是为了告诉子元素:“如果你要绝对定位,就以我为参照。”

2. 绝对定位:离开队伍,在指定房间找坐标

绝对定位会使元素脱离文档流。其他元素排版时不再为它保留位置,因此它可能覆盖在普通内容上方,也可能被其他元素覆盖。以下面的HTML代码为例:

<style>
        .box{
            width: 300px;
            height: 300px;
            border: 2px solid #000;
            position: relative;  // 开启定位
        }
        .wrapper{
            background-color: #8d1e80;
            position: absolute;  //设置为绝对定位
            left: 50%;           //相对 “box” ,向右偏移 “box” 的50%宽度
            top: 50%;            //相对 “box” ,向下偏移 “box” 的5高度
            transform: translate(-50%, -50%)   //相对 “wrapper”自身 ,向右和向下偏移自身的50%宽高
        }
 </style>
 
 <div class="box">
        <div class="wrapper">hello</div>
        <div class="name">zmjjkk</div>
 </div>
 <h2>标题</h2>

image.png

绝对定位不会简单地以自己原来的位置为参照。它会向外寻找最近的、已经开启定位的祖先元素(这个例子中我们将 wrapper 和 name 的父亲元素 box 设置成了相对定位)并以这个祖先作为定位范围。如果找不到合适的定位祖先,它通常会以 body 作为参照。

并且会脱离原来的文档流,所以我们可以看到wrappername都是块级元素,本来应该到wrappername各占据box中的一行并且wrapper应该在name的上面,但现在是wrapper占据了原本name的位置。

可以把它想成一名拿到特殊通行证的演员:他不再跟随普通队伍上场,而是在指定舞台中按照坐标直接就位。舞台由哪个父元素提供,取决于最近的定位祖先是谁。

绝对定位适合以下场景:

  • 容器角落的提示角标;
  • 图片上的文字或按钮;
  • 弹窗内部的关闭按钮;
  • 局部区域中的精确居中与覆盖效果。

它不适合承担整页的常规排版。因为元素一旦大量脱离文档流,内容变化时就很难自动推开彼此,页面容易出现重叠和错位。

3. 固定定位:钉在浏览器窗口上

固定定位通常以浏览器可视区域作为参照,并且会脱离文档流

页面滚动时,普通内容会跟随页面移动,而固定定位元素在视口中的位置通常保持不变。它像一张贴在浏览器玻璃上的便签:玻璃后面的页面可以滚动,便签却一直停留在眼前。如下面的HTML所示:

<style>
        body{
            height: 1000px;
        }
        .box{
            width: 100px;
            height: 100px;
            background-color: red;
            position: fixed;   //设置为固定定位
            bottom: 10px;      // 距离底部10px
            right: 10px;       // 距离最右边10px
        }
</style>
 
<body>
    <div class="box"></div>
    <h2>hello</h2>
</body>

image.png

固定定位常用于:

  • 返回顶部按钮;
  • 悬浮客服入口;
  • 始终可见的顶部或底部工具栏;
  • 覆盖整个屏幕的遮罩层。

使用固定定位时,需要特别注意内容遮挡问题。因为它已经脱离文档流,其他内容不会主动为它腾出空间。桌面端看起来大小合适的悬浮元素,在手机屏幕上可能会挡住正文或操作按钮。

4. 黏性定位:先随队伍移动,到线后吸住

黏性定位可以理解为相对定位固定定位的结合。

在元素还没有滚动到指定边界时,它会跟随页面正常移动;当它即将越过设定的吸附位置时,就会暂时停留在滚动区域的边缘;当所属容器的边界到来后,它又会受到容器范围的限制。 如下面的HTML代码所示:

<style>
        body{
            height: 200vh;
        }
        .box1{
            width: 500px;
            height: 500px;
            background-color: rgb(172, 106, 159);
        }
        .box2{
            height: 100px;
            background-color: aqua;
            position: sticky; //设置为黏性定位
            top: 0;           //当这个元素滚动到距离滚动容器顶部 0px 的位置时,就不要再继续往上滚了,黏在这里
        }

  </style>
    
<body>
    <div class="box1"></div>
    <div class="box2"></div>
</body>

image.png

它就像一块放在白板上的磁铁:平时跟着白板一起移动,碰到指定边缘时会暂时吸住,但它始终不能脱离自己所在的白板。

黏性定位通常不会脱离文档流,原本占据的空间会继续保留,因此非常适合制作:

  • 吸顶导航;
  • 文章中的章节标题;
  • 通讯录中的分组名称;
  • 长表格中需要持续显示的表头。

最后,用一张表概括四种定位方式:

定位方式主要参照物是否脱离文档流核心印象
相对定位自己原本的位置人挪走了,座位还在
绝对定位最近的定位祖先离开队伍,在指定房间找坐标
固定定位浏览器可视区域钉在浏览器窗口上
黏性定位滚动区域与所属容器先随页面移动,到线后吸住

三、定位元素重叠时:理解 z-index

元素发生定位后,很容易出现互相重叠。此时除了“放在哪里”,还需要决定“谁显示在上面”。这就是层叠顺序要解决的问题。

通常情况下,层级数值越大的元素越靠近观察者。不过,把它简单理解为“数字大的一定在上面”并不准确,因为层级比较受到层叠上下文限制。

层叠上下文可以想象成一栋栋独立的大楼。子元素只能先在自己的大楼内部排楼层,而两栋大楼谁在前面,要先看大楼本身的顺序。即使某个子元素在自己楼里住在一万层,也不一定能盖住另一栋整体位于前方的大楼。

理解层叠顺序时,可以记住以下几点:

  • 元素重叠且没有额外设置层级时,下面的标签比上面的标签优先级更高,后面的会盖住前面的。
  • 层级z-index要生效,必须要配合定位属性position
  • 层级只能在兄弟元素之间相比较,自己的子元素与兄弟元素的层级无法相比较;