CSS - 布局 - 元素定位

1,188 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

标准流 - Normal Flow

默认情况下,元素都是按照normal flow(标准流、常规流、正常流、文档流【document flow】)进行排布

  1. 块级元素独占一行,多个行内元素或行内块级元素可以在同一行进行显示
  2. 元素按照从左到右、从上到下按顺序依次摆放好
  3. 默认情况下,元素互相之间不存在层叠现象

LhYZHy.png

在标准流中,可以使用margin、padding对元素进行定位

但是在标准流中,设置一个元素的margin或者padding,通常会影响到标准流中其他元素的定位效果

此外在标准流中,不便于实现元素层叠的效果

为此CSS 提供了position属性,来帮助我们实现将一个元素脱离标准流(脱标),并对其进行定位操作

position属性可选值

说明
static静态定位,
默认值,
不脱标, 即布局为 normal flow
relative相对定位, 不脱标
absolute绝对定位, 脱标
fixed固定定位, 脱标
sticky粘性定位,可以在脱标和不脱标之间切换

如果一个元素设置了除static之外的position

那么我们就认为这个元素实现了脱标,是一个定位元素 (positioned element)

对于定位元素, 我们可以设置left 、right、top、bottom等属性值来实现位置调整

相对定位 - relative

.box {
  /*
  	通过将position的值设置为relative
  	来为某个元素开启相对定位
  */
  position: relative;
}
  • 相对定位不会脱离标准流,因此其元素本身依旧占位

  • 可以通过left、right、top、bottom进行定位

  • 对于开启了相对定位的元素,其设置了left、right、top、bottom的效果

    类似于margin-left, margin-right, margin-top, margin-bottom

  • 定位参照对象是元素自己原来的位置

相对定位的应用场景是:在不影响其他元素位置的前提下,对当前元素位置进行微调

示例

LhrSIL.png

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    span {
      position: relative;
      font-size: 12px;
      bottom: 5px;
    }
  </style>
</head>
<body>
  2<span>3</span> + 3<span>2</span> = 17
</body>
</html>

固定定位 - fixed

.box {
  /*
  	通过将position的值设置为fixed
  	来为某个元素开启相对定位
  */
  position: fixed;
}
  • 元素脱离normal flow (脱离标准流、脱标)
  • 可以通过left、right、top、bottom进行定位
  • 定位参照对象是视口(viewport),
    • 当画布滚动时,因为viewport是固定不动,所以开启了固定定位的元素也会固定不动

示例:

LhrYcf.png

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .operate {
      position: fixed;
      right: 30px;
      bottom: 30px;
    }

    .btn {
      width: 60px;
      height: 30px;
      line-height: 30px;
      text-align: center;
      background-color: gray;
      color: #fff;
      border-radius: 5px;
    }

    .top {
      margin-bottom: 5px;
    }
  </style>
</head>
<body>
  <div class="operate">
    <div class="btn top">顶部</div>
    <div class="btn feedback">反馈</div>
  </div>
</body>
</html>

绝对定位 - absolute

  1. 开启了绝对定位的元素脱离normal flow (脱离标准流、脱标)
  2. 可以通过left、right、top、bottom进行定位
    • 定位参照对象是最邻近的定位祖先元素
    • 如果找不到这样的祖先元素,参照对象是视口

开启了绝对定位的元素的定位参照元素是最近的那个定位元素,也就是最近的那个position不是fixed的元素

而我们绝大多数情况下,都希望父元素不脱标,只是作为绝对定位元素的定位参照元素,

所以在绝大多数情况下,绝对定位元素的定位参照元素开启的都是相对定位

因为相对定位成为了定位元素,但不脱标,此时我们可以简称为 子绝父相

绝对定位元素特点

  1. 可以随意设置宽高且 元素宽高默认由内容决定
    • 此时看上去 特性和行内块级元素的特性是一致的,但是它不是行内块级元素
    • 当一个元素的position值为absolute或者fixed的时候,这个元素就被称之为 绝对定位元素(absolutely positioned element)
  2. 不再受标准流的约束
  • 不再严格按照从上到下、从左到右排布
  • 不再严格区分块级(block)、行内级(inline),行内块级(inline-block)
  1. 不再给父元素汇报自己的宽度或高度
  2. 脱标元素内部默认还是按照标准流布局

此外,对于绝对定位元素来说,存在如下计算方式

定位参照对象的宽度 = left + right + margin-left + margin-right + 绝对定位元素的实际占用宽度

定位参照对象的高度 = top + bottom + margin-top + margin-bottom + 绝对定位元素的实际占用高度

利用上边的特性,我们可以实现如下效果:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .father {
      width: 300px;
      height: 300px;
      background-color: red;
      position: relative;
    }

    /*
      此时div.child的参照定位元素为box.father

      fatherWidth = childWidth + left + right + marginLeft + marginRight
			因为 marginLeft 和 marginRight的默认值是0
      得到300 = auto + 0 + 0 + 0 + 0
			所以在这里 auto的值为 300
			因此auto的本质是 由浏览器来决定需要显示的具体值是多少

      高度同理
    */
    .child {
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      background-color: blue;
    }
  </style>
</head>
<body>
  <!-- 绝对定位元素的宽高和定位参照对象一样 -->
  <div class="father">
    <div class="child"></div>
  </div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .father {
      width: 300px;
      height: 300px;
      background-color: red;
      position: relative;
    }

    /*
      此时div.child的参照定位元素为box.father
      tips:
    		1. 一定要设置元素的宽度和高度
    		2. margin四个方向的值都需要设置为auto

      fatherWidth = childWidth + left + right + marginLeft + marginRight
      此时设置childWidth的值为100
      得到 300 = 100 + 0 + 0 + 0 + 0
      公式不成立 => 默认情况下,浏览器会把剩余的值全部赋值给marginRight
      但是这里设置margin各个方向上的值为auto
      => 300 = 100 + 0 + auto + auto
      => 2auto = 300
      => auto = 150
      => marginLeft = marginRight = 150
      => div.child相对于div.father 水平居中

      垂直方向同理
    */
    .child {
      width: 100px;
      height: 100px;
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      margin: auto;
      background-color: blue;
    }
  </style>
</head>
<body>
  <!-- 绝对定位元素在定位参照对象中居中显示 --- 同时满足水平居中和垂直居中 -->
  <div class="father">
    <div class="child"></div>
  </div>
</body>
</html>

粘性定位 - sticky

另外还有一个定位的值是position: sticky,比起其他定位值要新一些.

sticky可以看做是相对定位和固定(绝对)定位的结合体 --- 在展示效果上

它允许被定位的元素表现得像相对定位一样,直到它滚动到某个阈值点

当达到这个阈值点时, 就会变成固定(绝对)定位

sticky是相对于最近的滚动祖先元素,也就是sticky是相对于滚动视口( scrollport )的

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      body {
        height: 200vh;
      }

      .scrollview {
        width: 300px;
        height: 300px;
        margin: 100px auto;
        border: 1px solid red;
        overflow: auto;
      }

      h2 {
        margin: 10px 0;
        padding: 0;
        text-align: center;
      }

      .box {
        background-color: #f00;
        color: #fff;
        /*
        距离最近的滚动父元素
        在这里就是div.scrollview
        */
        position: sticky;
        /*
        当当前元素的上边框和最近的父级滚动元素重合的时候
        由相对定位转变为固定定位
        */
        top: 0;
      }
    </style>
  </head>
  <body>
    <div class="scrollview">
      <h2>lorem</h2>
      <div class="box">this is navbar</div>
      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ea, aperiam.
      Fuga recusandae laboriosam cumque quasi eum molestias asperiores a minima?
      Consequatur alias quam placeat hic neque esse nisi voluptate quas!
      Eveniet doloremque enim ducimus quas tempora vel sit iure facere.
      Quis blanditiis quibusdam, explicabo qui dolores possimus dignissimos modi dolorum.
      Debitis, ex quo! Similique aut, impedit est eos qui molestiae.
      Ipsa explicabo voluptate expedita dolor obcaecati! Natus libero corrupti excepturi?
      Rerum optio quas cum eaque architecto cumque itaque veniam sit?
      Ad itaque aliquid quaerat error ipsum accusantium nesciunt minus et.
      Iste vitae non vero dicta cupiditate dolorem maiores. Dolorum, obcaecati.
    </div>
  </body>
</html>

注意:

  1. 设置了sticky的元素对应的包含块是父元素,如果父元素滚动离开了页面,那么sticky的元素也会随着父元素一起滚离页面
  2. sticky需要结合top,right,bottomleft 四个阈值其中之一,否则其行为与相对定位相同
  3. 设置了sticky的祖先元素不能设置overflow属性,也就是说祖先元素的overflow属性的值必须是visible,否则sticky属性会静默失效

postiton各值比较

LhZQAk.png

sticky在效果上是在多个定位状态之间进行切换,所以没有列在表中

对于定位元素,当脱标后,不设置left,right,top,bottom的时候,他们的默认值皆为auto

表现形式为

  1. 尽可能到达父级定位元素的左上角(也就是left和top值为0)
  2. 定位元素后边的元素上移
  3. 定位元素之前的元素不动,且不会被定位元素覆盖

LhZ4eH.png

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      span {
        position: absolute;
        width: 50px;
        height: 50px;
        background-color: rgba(0, 0, 0, 0.5);
      }
    </style>
  </head>
  <body>
    <div class="before"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui, consequatur. </div>
    <!--
      在定位元素的left,top,rignt,bottom都不设置的情况下
      默认情况下,会尽可能实现left和top的值为0
      但定位元素不会覆盖其之前的元素,所以span元素不会覆盖div.before
      定位元素后边的元素会上移,所以span元素会和div.after元素发生层叠
			所以会发现示意图中,灰色的盒子和第二行文本有重叠
    -->
    <span></span>
    <div class="after">Animi, cum! Itaque numquam rem modi nisi perferendis dolores praesentium!</div>
  </body>
</html>

z-index

对于定位元素,默认情况下,写在后面的那个元素会层叠在写在前面的那个元素上

如果我们需要调整定位元素的层叠顺序,我们可以使用z-index

z-index可以取正整数、负整数、0

z-index的默认值为auto,表现效果和z-index的值为0的时候一致

LhZYK6.png

z-index的比较原则

  1. 对于兄弟定位元素
    • z-index越大,层叠在越上面
    • z-index相等,写在后面的那个元素层叠在上面
  2. 如果不是兄弟关系
    • 各自从元素自己以及祖先元素中,找出最邻近的那几个兄弟定位元素来进行比较
    • 而且这几个兄弟定位元素必须有设置z-index

LhZJaX.png

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      .box {
        width: 100px;
        height: 100px;
        color: #fff;
        position: absolute;
      }

      .box1 {
        background-color: #f00;
        z-index: 1;
      }

      .box2 {
        background-color: #0f0;
        left: 20px;
        top: 20px;
        z-index: 2;
      }

      .inner-box {
        width: 50px;
        height: 50px;
        background-color: yellow;
        color: red;
        position: absolute;
        /*
          在这里虽然div.inner-box的z-index为999
          但是其没有兄弟定位元素
          因此找到最邻近的定位元素进行比较
          分别为 div.box1 div.box2 div.box3
          此时div.box2的层级在div.box3下
          所以即使div.inner-box的z-index的值再大
          其还是会被div.box3元素覆盖
        */
        z-index: 999;
      }

      .box3 {
        background-color: #00f;
        left: 40px;
        top: 40px;
        z-index: 3;
      }
    </style>
  </head>
  <body>
    <div class="box box1">1</div>
    <div class="box box2">
      <div class="inner-box">2</div>
    </div>
    <div class="box box3">3</div>
  </body>
</html>