3.4 定位

CSS布局的核心是position属性,对元素盒子应用这个属性,可以相对于它在常规文档流中的位置重新定位。position属性有4个值:staticrelativeabsolutefixed,默认值为static。这些属性都是什么意思?别急,我会通过以下4个段落来逐个说明。

  1. <p>First Paragraph</p>
  2. <p>Second Paragraph</p>
  3. <p id="specialpara">Third Paragraph (with ID)</p>
  4. <p>Fourth Paragraph</p>

在接下来的例子中,我会让第一段、第二段和第四段保持默认的static定位方式,但修改第三段的position属性。

为了不影响其他段落,我特意为第三段添加了值为specialpara的ID。

3.4.1 静态定位

我们先看一看图3-23,这是四个段落都采用默认静态定位的效果。

enter image description here图3-23 静态定位下的块级元素会在默认文档流中上下堆叠

在静态定位的情况下,每个元素在处在常规文档流中。它们都是块级元素,所以就会在页面中自上而下地堆叠起来。想想第1章我们展示的那个没有应用样式的HTML布局,那就是默认的static文档流。

要想突破static定位提供的这种按顺序布局元素的方式,必须把盒子的position属性改为其他三个值。

3.4.2 相对定位

下面我们就把第三段的position属性设置为relative。光设置这个属性还看不出来有什么不一样,因为你只设置了它的定位方式是“相对定位”。到底相对哪里定位呢?相对的是它原来在文档流中的位置(或者默认位置)。接下来,可以使用toprightbottomleft属性来改变它的位置了。但多数情况下,只用topleft就可以实现我们想要的效果。以下CSS规则

  1. p#specialpara {position:relative; top:25px; left:30px;}

可以得到图3-24所示的结果。

enter image description here图3-24 相对定位下,可以利用topleft属性相对于元素在文档流中的常规位置重新定位

可以给topleft属性设定负值,把元素向上、向左移动。

现在,第三段与它在文档流中的默认位置相比,向下移动了25像素,向右移动了30像素。相当于它把自己从原来的包含元素(body)中挣脱出来了,而且有一部分还跑到了屏幕之外。要注意,除了这个元素自己相对于原始位置挪动了一下之外,页面没有发生任何变化。换句话说,这个元素原来占据的空间没有动,其他元素也没动。

使用相对定位的关键是什么呢?就是要考虑到元素原来的空间。如图3-24所示,可以给第四段设置一个30像素或更大的margin-top值,让它向下移动,从而避免被重新定位的第三段挡住。

3.4.3 绝对定位

绝对定位跟静态定位和相对定位比,绝对不一样。因为绝对定位会把元素彻底从文档流中拿出来。好,下面我们就修改例子中的代码,把relative改成absolute

  1. p#specialpara {position:absolute; top:25px; left:30px;}

图3-25显示了结果。

enter image description here图3-25 绝对定位下,元素从文档流中被“连根拔起”,然后再相对于其他元素(在这里,是默认的定位上下文body)定位

在图3-25中,可以看到元素之前占据的空间被“回收了”。这说明,绝对定位的元素完全脱离了常规文档流,它现在是相对于顶级元素body在定位。而这自然而然就引出了一个关于定位的重要概念:定位上下文。

关于定位上下文,首先我们要知道绝对定位元素默认的定位上下文是body元素。如图3-25所示,通过topleft设定的偏移值,决定了元素相对于body元素(标记层次中的祖先容器),而不是相对于它在文档流中的位置偏移多远——这一点与相对定位的元素不同。

由于绝对定位元素的定位上下文是body,所以在页面滚动的时候,为了维护与body元素的相对位置关系,它也会相应地移动。

在介绍怎么给绝对定位元素设定其他定位上下文(body之外的元素)之前,我们先把4种定位方式介绍完。接下来看一看固定定位。

3.4.4 固定定位

从完全移出文档流的角度说,固定定位与绝对定位类似。

  1. p#specialpara {position:fixed; top:30px; left:20px;}

但不同之处在于,固定定位元素的定位上下文是视口(浏览器窗口或手持设备的屏幕),因此它不会随页面滚动而移动。图3-26和图3-27展示了固定定位的效果。

enter image description here图3-26 乍一看,固定定位很像绝对定位……

enter image description here图3-27 ……但滚动页面才发现,固定定位元素不随着页面滚动而移动

固定定位并不常用,最常见的情况是用它创建不随页面滚动而移动的导航元素。

好了,既然你已经理解了4个定位属性,那接下来我们好好讲一讲定位上下文吧。

3.4.5 定位上下文

把元素的position属性设定为relativeabsolutefixed后,继而可以使用toprightbottomleft属性,相对于另一个元素移动该元素的位置。这里的“另一个元素”,就是该元素的定位上下文。

在讲绝对定位的时候,我们知道绝对定位元素默认的定位上下文是body。这是因为body是标记中所有元素唯一的祖先元素。而实际上,绝对定位元素的任何祖先元素都可以成为它的定位上下文,只要你把相应祖先元素的position设定为relative即可。

比如下面的标记

  1. <body>
  2. <div id="outer">
  3. <div id="inner">This is text…</div>
  4. </div>
  5. </body>

请注意,对HTML中的文本应该使用恰当的语义标签来标记。我们这里为了说明问题的需要,才把文本直接放在了没有语义的div中。

搭配下面的CSS

  1. div#outer {width:250px; margin:50px 40px; border-top:3px solid red;}
  2. div#inner {top:10px; left:20px; background:#ccc;}

结果如图3-28所示。

enter image description here图3-28 这里是两个嵌套在一起的div。我们给外部div的上方加了边框,给内部div加了背景。由于内部div(默认)是静态定位的,因此topleft属性不起作用

看到代码里给内部div设定了topleft属性,你是不是觉得图3-28有问题——为什么内部div没有相对外部div向下移动10像素,向右移动20像素呢?为什么它们俩的原点(左上角点)还一样呢?原因在于,内外部div默认都是静态定位,它们之间不存在谁是谁的定位上下文这个问题。换句话说,在常规文档流中,由于外部div没有内容,内部div就会跟它共享相同的起点。只有将元素的position属性设定为relativeabsolutefixed,这个元素的toprightbottomleft属性才会起作用。下面我们就把内部div设定为绝对定位,来看一看有什么变化发生。

  1. div#outer {width:250px; margin:50px 40px; border-top:3px solid red;}
  2. div#inner {position:absolute; top:10px; left:20px; background:#ccc;}

对了,绝对定位相对于谁呀?由于没有相对定位的祖先元素供其参照,内部div只能以默认的定位上下文body作为参照,相对于它定位。此时,内部div完全无视其父元素(外部div)的存在,topleft属性会相对于body元素向下、向左偏移其位置,结果如图3-29所示。

enter image description here图3-29 虽然有背景的内部div在标记中位于外部div(看那个边框)之中,但由于不存在相对定位的其他祖先元素可以作为定位上下文,绝对定位的内部div只能相对于body元素进行定位

事实上,只要把元素的外边距和内边距设定好,多数情况下只用静态定位就足以实现页面布局了。很多刚开始接触CSS的初学者都会错误地设定position属性,最终才发现从文档流中挪出来的这些元素一点也不好控制。因此,除非真需要那么做,否则不要轻易修改元素默认的position属性。

如果我现在把外部divposition属性设定为relative

  1. div#outer {position:relative; width:250px; margin:50px 40px; border-top:3px solid red;}
  2. div#inner {position:absolute; top:10px; left:20px; background:#ccc;}

这样,绝对定位的内部div的定位上下文就变成了外部div,结果如图3-30所示。

enter image description here图3-30 外部div改为相对定位之后,其后代中绝对定位的元素就会按照topleft属性的设定,相对于外部div定位

此时内部divtopleft属性参照的就是外部div了。如果你再用lefttop属性重新定位外部div,内部div也会跟着移动相同的距离,以保证它与外部div(也就是它的定位上下文)之间的位置关系。