likes
comments
collection
share

修炼CSS内功——CSS中的流与定位

作者站长头像
站长
· 阅读数 39

什么是流

页面中的元素默认是如何排列呢?为何有的元素我们没有设置宽度却自动填满一行?种种这些疑问都让我们要去探索页面布局的核心:流。 什么是流?在W3C的文档中将CSS的基本定位机制和布局方式称为标准流(或称标准文档流),不过张鑫旭老师在《CSS世界》中将之称为流,这种说法更能凸显出这种布局方式像水流一样自然自动,因此本篇博客也采用了流这个说法,因此“流”就是是html元素默认的布局机制,是html元素没有添加任何CSS属性时的布局方式。

块级元素与内联元素

HTML按照功能将标签一般分为容器级标签与文本级标签,常见的标签列举如下:

  • 文本级标签:p、span、a、b、i、u、em
  • 容器级标签:div、h1~h5、li、dt、dd

其中,除了p以外的所有文本级标签都是内联元素,所有的容器级标签都是块级元素。从表现来说,内联元素的典型特征就是可以和文字在一行显示。实际上文字也是内联元素,图片、按钮、输入框、下拉框等元素也是内联元素。实际开发中,我们经常把 display 计算值为 inline inline-block inline-table table-cell 的元素叫做内联元素,而把 display 计算值为 block、table 的元素叫做块级元素。注意 li 元素的 display 属性为 list-item,它也是块级元素。

块级元素和内联元素的主要区别为:

  • 对于块级元素,流体布局之下 width: auto 自适应撑满父元素宽度。这里的撑满并不同于 width: 100% 的固定宽度,而是像水一样能够根据 margin 不同而自适应父元素的宽度。如果给块级元素指定的宽度过小则会由 margin 自动填充,过大则页面会自动添加 scroll。
  • 对于内联元素,width: auto 则呈现出包裹性,内联元素不能设置宽高,宽度由子元素的宽度决定。
  • 无论内联元素还是块级元素,height: auto 都是呈现包裹性,即高度由子级元素撑开。
  • 注意父元素 height: auto 会导致子元素 height: 100% 百分比失效。

注:正常流下,如果块级元素的 width 是个固定值,margin 是 auto,则 margin 会撑满剩下的空间;如果 margin 是固定值,width 是 auto,则 width 会撑满剩下的空间。这就是流体布局的根本所在。

元素的定位

在实际页面开发中我们往往要对页面进行各种布局,因此我们可能需要使某个元素脱离流布局的限制,目前为止 CSS 中有两种手段使一个元素脱离标准文档流,一种是浮动,另一种就是绝对定位,下面我们介绍元素的定位。 CSS3 中 position 属性可选的值如下:

  • static:默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index声明)。
  • relative:生成相对定位的元素,相对于其正常位置进行定位,遵循标准文档流,但将依据top,right,bottom,left等属性在正常文档流中偏移位置,而其层叠通过z-index属性定义。
  • absolute:生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位,脱离标准文档流,元素的位置通过 left, top,right 以及bottom属性进行规定,其层叠通过z-index属性定义。
  • fixed:生成绝对对定位的元素,相对于浏览器窗口进行定位,脱离标准文档流,元素的位置通过 left, top, right以及bottom 属性进行规定,当出现滚动条时,对象不会随着滚动,其层叠通过z-index属性定义。
  • inherit:规定应该从父元素继承 position 属性的值。

1、position: static

默认值,无特殊定位,遵循正常的流特性,在静态定位模式下 top、right、left、bottom及z-index 属性都是无效的。

2、position: absolute

absolute 绝对定位脱离了标准文档流,元素可以通过 left、top、right 和 bottom 属性进行定位,定位参考元素为第一个非 static 定位的祖先元素,这儿需要注意如果该祖先元素是纯 inline 元素,则规则略复杂:

  • 假设给内联盒子的前后各生成一个宽度为零的内联盒子,则这两个内联盒子的 padding box 外面的包围盒就是内联元素的定位和计算的参考元素;
  • 如果该内联元素被跨行分割了,那么包含块是未定义的,也就是 CSS 2.1规范并没有明确定义,浏览器自行发挥。

否则,进行定位和计算的参考元素由该祖先的 padding box 边界形成。如果向上一直没有找到符合条件的祖先元素则以根元素(大多数情景下可以直接视为 html 元素,其尺寸等同于浏览器可视窗口的大小)作为参考,absolute 绝对定位也支持通过 z-index 属性来设置层级。 position: absolute 的特性可以概括为“块状化”,“包裹性”和“破坏性”。 首先是“块状化”,position: absolute 的特性“块状化”和 float 属性类似,当一个元素的 position 属性值为 absolute 或 fixed,那么该元素的 display 属性的计算值就是 block 或 table。示例:

var span = document.createElement('span');
document.body.appendChild(span);
console.log(window.getCompoutedStyle(span).display); // inline
span.style.position = 'absolute';
console.log(windiw.getComputedStyle(span).display); // block

然后是“破坏性”,这一点也和 float 属性类似,它们都破坏了正常的流特性,但是虽然 absolute 破坏正常的流来实现自己的特性表现,但是本身还是受到普通的流体元素布局、位置乃至一些内联相关的 CSS 属性影响的,关于这种影响最典型的应用就是“无依赖的 absolute 绝对定位”。

很多人错误地认为凡是使用 absolute 绝对定位的地方,一定要将父容器的 position 属性设为 relative,同时通过 left/top 等属性进行定位,甚至必须同时使用 z-index 属性设置层级,但需要注意,absolute 是非常独立的 CSS 属性值,其样式和行为表现不依赖其他的任何 CSS 属性就可以完成。 而当不添加left、top等属性时元素仍出现在当前位置,(一般把这种没有设置 left/top/right/bottom 属性值的绝对定位称为“无依赖绝对定位”)示例:

<style type="text/css">
  #container {
    width: 200px;
    height: 200px;
    border: 2px solid gray;
  }
  #box {
    position: absolute;
  }
</style>
<body>
  <div id="container">
    <div id="box">absolute绝对定位</div>
  </div>
</body>

页面效果:

修炼CSS内功——CSS中的流与定位

这种“无依赖的absolute绝对定位”在大多数场景下要比使用 left/top 之类的属性定位要实用和强大得多,因为其除了其代码更加简洁外还有一个很棒的特性,就是“相对定位特性”,实际上“无依赖绝对定位”实质上就是相对定位,但是相较于相对于相对定位,它多了一条特性:不占据标准文档流的内存空间,我们通过下面示例展现“无依赖绝对定位”的强大之处:

<div class="nav">
    <h4 class="nav-list">
        <a href class="nav-a">普通导航</a>
    </h4>
    <h4 class="nav-list">
        <a href class="nav-a">
            热门导航<i class="icon-hot"></i>
        </a>
    </h4>
    <h4 class="nav-list">
        <a href class="nav-a">
            新导航<i class="icon-new"></i>
        </a>
    </h4>
</div>
.nav {
    display: table;
    table-layout: fixed;
    width: 100%;
    max-width: 600px;
    margin: 1em auto; 
    background-color: #333;
    text-align: center;
}
.nav-list { 
    display: table-cell;
    font-weight: 400;
}
.nav-a { 
    display: block; 
    line-height: 20px; 
    padding: 20px; 
    color: #bbb; 
    text-decoration: none; 
}
.nav-a:hover { 
    color: #fff; 
}
.icon-hot { 
    position: absolute; 
    width: 28px; height: 11px; 
    margin: -6px 0 0 2px; 
    background: url(hot.gif);
}
.icon-new { 
    position: absolute; 
    width: 12px; 
    height: 13px; 
    margin: -6px 0 0 2px; 
    background: url(new.png) no-repeat center;
}

页面效果:

修炼CSS内功——CSS中的流与定位

关于 absolute 定位的“包裹性”:absolute 天然具有“包裹性”(即尺寸收缩包裹,同时具有自适应性),但是,和 float 或其他的“包裹性”带来的“自适应性”相比,absolute 有一个平时不太被人注意的差异,那就是 absolute 的自适应性的最大宽度往往不是由父元素决定的,这是由于 absolute 定位和计算的参考元素的不同,示例:

<style>
    #container {
        width: 200px;
        border: 1px solid gray;
        position: relactive;
    }
    #box {
        position: absolute;
    }
</style>
<body>
    <div id="container">
        <div id="box">absolute绝对定位天然具有“包裹性”(即尺寸收缩包裹,同
        时具有自适应性),但是,和float或其他的“包裹性”带来的“自适应性”相比,
        absolute有一个平时不太被人注意的差异,那就是absolute的自适应性的最
        大宽度往往不是由父元素决定的,这是由于absolute定位和计算的参考元素的
        不同</div>
    </div>
</body>

页面效果:

修炼CSS内功——CSS中的流与定位

从上图我们可以看出,当绝对定位元素没有设置宽度相关属性的时候,其宽度由里面的元素和外面的进行定位和计算的参考元素共同决定,container 元素出现高度坍塌的原因是 absolute 绝对定位破坏了标准文档流,这也正是 absolute 绝对定位的破坏性。

3、position: relative

relative 的定位有两大特性:一是相对自身,二是无侵入。 “相对自身”是指 relative 定位中偏移位置以元素在原本标准文档流中应该出现的位置为参考;“无侵入”是指当 relative 进行定位偏移的时候,一般情况下不会影响周围元素的布局。 示例:

<html>
<head>
    <style type="text/css">
        #first{
            width: 200px; 
            height: 200px; 
            border: 2px solid red
        }
        #second{
            width: 200px; 
            height: 200px;
            border: 2px solid green;
            position: relative;
            top :50;
            left : 50
        }
    </style>
</head>
<body>
     <div id="first">静态定位</div>
     <div id="second">相对定位</div>
</body>
</html>

页面结果:

修炼CSS内功——CSS中的流与定位

relative 的定位还有另外两点值得一提,相对定位元素的 left/top/right/bottom 的百分比值是相对包含块计算的,而不是自身。注意,虽然定位位移是相对自身,但是百分比值的计算值不是。

top 和 bottom 这两个垂直方向的百分比值计算跟 height的百分比值计算是一样的,都是相对高度计算的。同时,如果包含块的高度为 auto,那么计算值是 0,偏移无效,也就是说如果父元素没有设定高度或者不是“格式化高度”,那么 relative 类似 top: 20% 的代码等同于 top: 0

当相对定位元素同时应用对立方向定位值的时候,也就是 top/bottom 和 left/right 同时使用的时候,其表现和绝对定位差异很大,绝对定位是尺寸拉伸,保持流体特性,但是相对定位却是“你死我活”的表现,也就是说,只有一个方向的定位属性会起作用,而孰强孰弱则是与文档流的顺序有关,默认的文档流是从上而下,从左往右,因此 top/bottom 同时使用的时候, bottom 被干掉,left/right 同时使用的时候,right 毙命。 示例:

.examole {
    position: relative;
    top: 10px;
    left: 10px;
    right: 10px; /*无效*/
    bottom: 10px; /*无效*/
}

4、position: fixed

position: fixed 固定定位元素的“包含块”是根元素,我们可以将其近似看成 <html>元素。换句话说,唯一可以限制固定定位元素的就是 <html> 根元素。所以如果是想把某个元素固定定位在某个模块的右上角,下面这种做法就是没有用的:

<div class="father">
    <div class="son"></div>
</div>
.father {
    width: 300px;
    height: 200px;
    position: relative;
}
.son {
    width: 40px;
    height: 40px;
    position: fixed;
    top: 0;
    right: 0
}

但是,并不是说我们无法把 .son 元素精确定位到 .father 元素的右上角,事实上是可以实现的,如何实现呢?和“无依赖绝对定位”类似,就是无依赖的固定定位,利用 absolute/fixed 元素没有设置 left/top/right/bottom 的相对定位特性,可以将目标元素定位到我们想要的位置,例如:

<div class="father">
    <div class="right">
        &nbsp;<div class="son"></div>
    </div>
</div>
.father {
    width: 300px;
    height: 200px;
    position: relative
}
.right {
    height: 0;
    text-align: right;
    overflow: hidden;
}
.son {
    display: inline;
    width: 40px;
    height: 40px;
    position: fixed;
    margin-left: -40px;
}

注:position: fixed 的 absolute 模拟

有时候我们希望元素既有不跟随滚动的固定定位效果,又能被定位元素限制和精准定位,那我们该怎么办呢?

我们可以使用position: absolute进行模拟,原理其实很简单,页面的滚动使用普通元素代替,此时滚动元素自然就有了”固定定位“的效果了。常规的html结构和css代码是下面这样的:

<html>
    <body>
        <div class="fixed"></div>
    </body>
</html>
.fixed {
    position: fixed;
}

使用 position: absolute 进行模拟则需要一个滚动容器,假设类名是 .page,则有:

<html>
    <body>
        <div class="page">固定定位元素</div>
        <div class="fixed"></div>
    </body>
</html>
html,body {
    height: 100%;
    overflow: hidden;
}
.page {
    height: 100%;
    overflow: auto;
}
.fixed {
    position: absolute;
}

整个网页的滚动条由 .page 元素产生,而非根元素,此时虽然 .fixed 元素是绝对定位,但是并不在滚动元素内部,自然滚动时不会跟随,如同固定定位效果,同时本身绝对定位,因此,可以使用 relative 进行限制或者 overflow 进行裁剪等。然而,将网页的窗体滚动变成内部滚动,很多窗体滚动相关的小 JavaScript 组件需要跟着进行调整,并且可能会丢失其他一些浏览器内置行为,需要谨慎使用。

参考资料

《CSS世界》张鑫旭