前言

聊起absolute定位,我们首先想到的就是找其包含块(containing block)或直接给父元素一个relative定位,然后根据包含块通过设置top,right,bottom,left属性值进行偏移。看起来,这是很常见的做法,但盲目的如此”套公式“,只能说你对absolute的理解不到家。

何所谓“依赖”

所谓“依赖”,指的是绝对定位元素需要找到包含块,然后”依赖“包含块进行偏移或格式化元素的宽高。

为了达到我们的目的——让子元素紧紧地“依赖”父元素,我们会“丧心病狂”的硬塞给父元素一个relative定位,甚至配合z-index属性设置层级。

好吧,先不谈父元素愿意不愿意,既然谈到了“依赖”,就不得不联想到耦合,耦合度越高的代码在应对变化时就越脆弱。

传统的“有依赖”绝对定位

既然要聊聊无依赖绝对定位,那么就应该先看看传统的”有依赖”绝对定位在一些经典应用场景是怎么做的,然后我们再谈怎么改进。

表单提示信息

  1. 场景重现:填写表单时,当某个输入框获取焦点时,在输入框右侧显示一些提示信息。

  2. 场景分析:

    1. 提示信息为不定长,若父元素宽度表现为定长,那么有可能内容会溢出;若父元素宽度表现为包裹性,那么宽度就会改变。
    2. 提示信息的显示和隐藏不能影响原布局,那么使其脱离文档流是最常见的选择。
  3. 代码实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="utf-8">
    <title>demo1</title>
    <style>
    .regist-box {
    width: 300px;
    /* 容器定宽 */
    margin: 0 auto;
    /* 为了便于观察给一个边框 */
    border: 1px solid;
    /* 使其变成子元素的包含块 */
    position: relative;
    }

    input {
    width: 200px;
    }
    /* 提示信息 */

    .regist-remark {
    color: red;
    display: none;
    /* 绝对定位 */
    position: absolute;
    /* 根据包含块进行定位 */
    top: 0;
    left: 250px;
    white-space: nowrap;
    }
    /* 输入框获得焦点显示提示信息 */

    input:focus~.regist-remark {
    display: inline;
    }
    </style>
    </head>

    <body>
    <div class='regist-box'>
    <div class="regist-group">
    <label class="regist-label">邮箱</label>
    <input type="email" class="regist-input">
    <span class="regist-remark">请输入邮箱!</span>
    </div>
    </div>
    </body>

    </html>
  4. 眼见为实:

  5. 代码分析:
    让我们来分析一下,这样的代码是如何的脆弱。改动输入框width/padding/margin或改动label的文字长度,都需要修改提示信息的偏移值。那么他的复用性就大大降低了,特别是应对动态生成元素时,需要借助js进行偏移计算。

无依赖的改造

  1. 直接进入正题,对于上述应用场景的代码进行改造。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54

    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="utf-8">
    <title>demo2</title>
    <style>
    .regist-box {
    width: 300px;
    /* 容器定宽 */
    margin: 0 auto;
    /* 为了便于观察给一个边框 */
    border: 1px solid;
    /* 使其变成子元素的包含块 */
    /* position: relative; */
    }

    input {
    width: 200px;
    }
    /* 提示信息 */

    .regist-remark {
    color: red;
    display: none;
    /* 绝对定位 */
    position: absolute;
    /* 根据包含块进行定位 */
    /*top: 0;
    left: 250px;
    white-space: nowrap;*/
    /* 利用margin进行偏移 */
    margin-left: 10px;
    }
    /* 输入框获得焦点显示提示信息 */

    input:focus~.regist-remark {
    display: inline;
    }
    </style>
    </head>

    <body>
    <div class='regist-box'>
    <div class="regist-group">
    <label class="regist-label">邮箱</label>
    <input type="email" class="regist-input">
    <span class="regist-remark">请输入邮箱!</span>
    </div>
    </div>
    </body>

    </html>
  2. 眼见为实

  3. 代码分析:
    毫不犹豫的去掉硬塞给父元素的相对定位,同时也去掉了left/right,使用margin进行元素偏移。
    代码中使用margin-left:10px从视觉上看就是相对于输入框向右偏移了10px,虽然实际上其脱离了文档流,但是margin的表现却如它还在文档流中一样。
    如此一来,没有了“依赖”,其弹性大大提高。无论你label有多少文字、input有多宽,我都永远乖乖跟在你身后,不离不弃。

  4. 小tip:
    想让提示信息放到输入框前面只需要改动它们在文档中的排列顺序哦!这个可以自行尝试。

    其他应用

    下面将给出它的其他简单有趣应用。

    悬停展示下拉列表

  5. 代码实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="utf-8">
    <title>demo3</title>
    <style>
    .item {
    display: inline-block
    }

    .item,
    .combo_box {
    padding: 10px;
    border: 1px solid;
    width: 100px;
    }

    .combo_box {
    position: absolute;
    /* margin-left = -1 * (padding-left + border-left) */
    margin-left: -11px;
    /* margin-top = padding-bottom + border-bottom */
    margin-top: 11px;
    border-top: none;
    display: none;
    }

    .item:hover>.combo_box {
    display: block;
    }
    </style>
    </head>

    <body>
    <div class='box'>
    <div class="item">
    <a>linka11111</a>
    <div class="combo_box">
    <div class="subItem">
    <a>linka111</a>
    </div>

    </div>
    </div>
    <div class="item">
    <a>linkb</a>
    <div class="combo_box">
    <div class="subItem">
    <a>linkb1</a>
    </div>
    <div class="subItem">

    <a>linkb2</a>
    </div>
    </div>
    </div>
    <div class="item">
    <a>linkc</a>
    </div>
    </div>
    </body>

    </html>
  6. 眼见为实:

  7. 代码分析:
    如此布局,可以满足一些简单的下拉框需求,仅当item的padding/border改变时需要对margin进行修改,而修改item内文字长度、大小都不会影响布局。

后记

无依赖absolute绝对定位的舞台很大,我们可以思考曾经使用absolute实现的功能是否都能利用它来替换,使代码更简洁更健壮。