CSS定位布局

108 阅读6分钟

目的

定位的作用主要是将元素从文档流中移走,允许将元素放到页面上的任意位置。那么我们经常说的文档流又是什么意思呢?

文档流可以理解为一种布局机制,在文档流中的元素分为block inline inline-block,所有的这些元素按照一定的规则进行布局,脱离文档流就不会应用这些元素排布规则

固定定位

固定定位是相对于视口进行定位的,设置position:fixed就可以放到页面的任意位置

  • 如果不设置width/height, 那么设置top / bottom / left / right 一起使用,也可以设置元素的大小
  • 如果设置了width/height
    • 不设置四个方向的值的时候,那么默认会给固定定位的元素添加top: 0px, left: 0px, 另外两个方向的值会动态计算
    • 如果同时设置了四个方向的值,那么会优先top / left生效,举个例子:
<!---->

    <!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>
          * {
            padding: 0;
            margin: 0;
          }
          body {
            height: 1600px;
            background-color: skyblue;
          }
          div {
            width: 400px;
            height: 400px;
            background-color: pink;
            position: fixed;
            top: 10px;
            left: 10px;
            right: 20px;
            bottom: 20px;
          }
        </style>
      </head>
      <body>
        <div></div>
      </body>
    </html>

绝对定位

绝对定位是相对于“包含块”进行定位,在固定定位中“包含块”可以看作是视口,绝对定位也是如此,只不过其包含块是最近的祖先定位元素,也是靠top / bottom / left / right 来定位

  • 如果没有找到,会相对于初始包含块来定位,这个初始包含块和视口一样大,固定在页面顶部 (这里的初始包含块到底是什么意思呢?是不是body呢)

相对定位

相对定位元素是相对于元素本来在文档流中的位置定位的(脱离文档流了吗?)

  • 使用top / bottom / left / right只能定位,不能设置大小,这点和固定定位和绝对定位的元素区别

层叠上下文和z-index

首先明白浏览器基本的渲染顺序是定位元素在非定位元素的前面,后面的元素在前面的元素前面。

如果想要控制层叠顺序,那么可能需要z-index,拥有较高z-index的元素出现在拥有较低z-index的元素前面。拥有负数z-index的元素出现在静态元素后面。

那么首先我们理解一下层叠上下文的概念,什么是层叠上下文呢?

  • 简单解释:一个层叠上下文包含一个元素或由浏览器一起绘制的一组元素,相当于确定了内部所有元素的渲染顺序
  • 但是需要注意一个问题,就是:如果一个元素叠放在一个层叠上下文前面,那么层叠上下文里没有元素可以被拉到该元素前面。
  • 做了一个实验:
<!---->

    <!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>
          * {
            padding: 0;
            margin: 0;
          }
          body {
            height: 1700px;
            background-color: skyblue;
          }
          [class^="item"] {
            background-color: pink;
            width: 50px;
            display: inline-block;
            border: 1px solid black;
            height: 50px;
          }

          .item2 {
            position: relative;
            left: 20px;
            top: 20px;
            z-index: 1;
          }

          .item2 span {
            width: 50px;
            height: 20px;
            z-index: 3;
            position: relative;
            display: inline-block;
            background-color: blueviolet;
          }

          .item3 {
            position: relative;
            left: -10px;
            z-index: 2;
            top: 10px;
          }
        </style>
      </head>
      <body>
        <div class="father">
          <div class="item1"></div>
          <div class="item2">
            <span>1</span>
            <span>2</span>
            <span>3</span>
          </div>
          <div class="item3"></div>
          <div class="item4"></div>
        </div>
      </body>
    </html>

这里踩了一个坑:把.item2设置成了0.9,发现效果和预期的不一样

查了一下发现:z-index不支持小数,W3C的语法来看:z-index: auto | ; 如果是小数,会被当作auto,同时不会创建层叠上下文

其次,用什么方式可以创建层叠上下文呢?

  • position: absolute / relative + z- index: auto以外的其他值
  • position: fixed / sticky
  • flex item / grid item + z-index : auto 以外的其他值
  • mix-blend-mode:非normal
  • 小于1的opacity
  • transform, scale, rotate, translate, filter, backdrop-filter, perspective, clip-path, mask / mask-image / mask-border属性
  • 文档根节点html
  • 等等

为什么要创建层叠上下文呢?

想象网页是一本书,每个层叠上下文是一本独立的小册子:

  • 你可以在小册子里随便排顺序(z-index)
  • 但册子之间的顺序是固定的,不能插页
  • 想改某个元素的位置,必须在它所在的小册子里动手
  • 最后浏览器把这些册子叠起来,变成你看到的完整页面

那么一组层叠上下文的元素会如何放置呢?

  • 层叠上下文的根(是文档流的第一块)
  • z-index为负的元素
  • 非定位元素(是文档流)
  • z-index: auto
  • z-index为正

(这里容易有个误区,以为非定位元素的z-index: auto,实际上非定位元素是没有z-index的,而设置了定位元素之后,z-index默认就是auto)

可以看作每个层叠上下文的内部都有自己的排布规则,但是整体的排序又是根据层叠上下文根的层级来定的,整体可以想象为一个嵌套结构

同时要明白创建层叠上下文不一定就是脱离了文档流,有些会有些不会

粘性定位

粘性定位可以看作是固定定位和相对定位的结合体,在正常情况下,元素随着屏幕滚动,到达一定阈值之后,就会“锁定”在屏幕的某个位置

粘性定位在我们的开发中用的也挺多的,但是好像一直对于细节不是很清楚,在开发中最多的是粘在顶部,比如这样:

<!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>
      * {
        padding: 0;
        margin: 0;
      }
      body {
        height: 1700px;
        background-color: skyblue;
      }
      div {
        position: sticky;
        margin-top: 600px;
        width: 100px;
        height: 100px;
        background-color: pink;
        bottom: 190px;
        /* top: 10px; */
        /* top: 20px;
        bottom: 20px;
        left: 20px;
        right: 20px; */
      }
    </style>
  </head>
  <body>
    <div></div>
  </body>
</html>

设置一个top值就可以在滚动的过程中,如何发现距离视口[top值]px的时候,一直固定在某个位置了

那么如何固定的底部呢?

  • 因为页面整体的滚动方向是从上往下,也就是说:浏览器默认是向上滚动的,所以浏览器会为它计算粘性触发点。所以可以把body当作一个明确的“滚动容器”
  • 但是这种情况下,底部粘性是无法生效的,必须显式给一个非 body 的父容器设置 overflow: auto/scroll;然后在子元素上设置 position: sticky; bottom: 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>
          * {
            padding: 0;
            margin: 0;
          }

          .container {
            width: 500px;
            height: 500px;
            background-color: pink;
            overflow: auto;
          }

          .item {
            width: 100px;
            height: 200px;
            background-color: aqua;
            border: 1px solid black;
          }

          .item1 {
            width: 100px;
            height: 100px;
            background-color: rgb(34, 13, 111);
            position: sticky;
            bottom: 0;
          }
        </style>
      </head>
      <body>
        <div class="container">
          <div class="item">1</div>
          <div class="item">2</div>
          <div class="item">3</div>
          <div class="item item1">4</div>
          <div class="item">5</div>
          <div class="item">6</div>
        </div>
      </body>
    </html>

视口概念

在之前的内容中,我们提到了是视口,初始包含块,相信大家也听说过一些其他的概念,布局视口以及理想视口等等,那么对于其中的细节一直都是一知半解,下一篇会详细介绍这里的概念~

Antd中相关组件设计

Ant Design(antd)中的组件设计非常注重可复用性和功能解耦。以 Popover 为例,它的定位实现依赖的是一个底层的 弹出层定位工具库 —— rc-trigger,而不是直接手写定位逻辑。

这里不过多介绍,如果想调试antd源码,可以查看www.zhihu.com(虽然我并没有成功,还在尝试中)

参考文献

Stacking context - CSS: Cascading Style Sheets | MDN

@layer | CSS-Tricks

《深入解析CSS》