天天看點

“位置”在css裡的細節

位置是個怎樣的概念

哎,這個元素怎麼跑那裡去了?

回想一下,在我們覺得“樣式崩了”,“頁面出bug了”的時候,是不是會有相當一部分情況都可以描述成上面這句話呢?

我們在寫css的時候,就會經常考慮“位置”這個事。理念就是,所有的頁面元素都應該被安排在為它預定的位置上。畢竟按照計劃預定的來,才能有條不紊,不容易出錯。

就像一本雜志的編輯,即便文稿都已準備好,但具體哪篇放在第幾頁的哪裡,是要認真考慮的。好的雜志應該有好的排版。

下面,本文将介紹一些會影響到“位置”,但一般不太會知道的要點。

盒模型的再認識

盒模型你一定很熟悉:

“位置”在css裡的細節

content

padding

border

margin

這些區域之外,請注意圖中的

edge

,也就是分隔盒模型各區域的邊。在确定頁面元素的準确位置時,需要細緻地參考這些邊。它們按照範圍從小到大分别是:

  • content edge,也叫做inner edge(注意和JavaScript的

    innerWidth

    等差別開)。它圍成的區域代表内容區,一般由元素内的具體内容決定。它确定的範圍叫做content box,也是css屬性

    box-sizing

    的預設值

    content-box

    的範圍。
  • padding edge。它确定padding box的範圍。盡管w3c規範已經從

    box-sizing

    移除了這個值,但很容易類比了解。
  • border edge。它确定border box的範圍。
  • margin edge,也叫做outer edge。它确定margin box的範圍。

    box-sizing

    也沒有這個值。

為什麼需要了解這些邊呢?想象一下你想要放置一個元素到你為它安排的位置上,但是,你拿的元素并不是一個點(比如實體學裡的質點),而是一個盒子。一個占據一定空間的盒子,如果沒有邊作為參照,你能清楚地知道應該怎樣去對齊嗎?

背景的位置

背景是很常用的樣式,但有好多地方可能不太會注意。

背景分為背景圖(

background-image

)和背景色(

background-color

),漸變(css gradients)也屬于背景圖。其中,背景圖位于背景色之上。如果使用多個背景圖(multiple backgrounds),聲明靠前的位于上方。

background-clip

之外的其他背景屬性,如

background-position

background-size

等,都隻作用于背景圖,對背景色沒有作用。這是可以了解的,因為背景色很單純,它沒有起點和終點,總是鋪滿整個空間,要麼有,要麼沒有。

現在,一個元素

div.bg-element

定義了這樣的css:

.bg-element{
    width: 100px;
    height: 100px;
    margin: 20px;
    padding: 20px;
    border: 5px dashed #386365;
    background: #2aace9 url(pattern.png) no-repeat;
}
           

這是一個同時有背景圖和背景色的元素,而且有内外邊距及邊框,其效果是:

“位置”在css裡的細節

可以看出,在預設情況下,背景圖的起始點為padding box的左上角,而背景色沒有起始點,将鋪滿整個border box(為了看到這一點,特意用了

dashed

邊框)。雖然這個例子中的背景圖好像限制在padding box範圍内,但實際上,它和背景色的可見範圍是一樣的,都是border box,用

background-position

background-repeat

稍作修改即可驗證:

“位置”在css裡的細節

這個背景可見範圍的概念,對應了css3新增屬性

background-clip

。這個屬性的預設值就是

border-box

。如果更改它,會是這樣:

“位置”在css裡的細節

無論怎麼修改可見範圍,這個例子中的背景圖的起始點都是padding box左上角,這就是背景圖起始點的概念。它對應的是css3中新增的

background-origin

,其預設值正是

padding-box

。如果修改了

background-origin

,那麼屬性

background-position

産生的位置偏移,包括

right

bottom

等關鍵字的情況,都會對應地改變參考的邊。

外邊距區域不會有背景。前文的盒模型的圖裡,

margin

transparent

一起也是這個意思。

自設容器進行定位

你一定用過這樣的定位搭配:

position: relative;

的父元素,加上

position: absolute;

的子元素。

這比較像建立一個xy平面坐标系,然後把元素放置在自己想要的坐标位置上。但是,x軸、y軸、原點、坐标點,它們分别在哪裡呢?

請看這個例子:

<div class="pos-container">
    <div class="pos-element"></div>
</div>
           
.pos-container{
    position: relative;
    width: 140px;
    height: 140px;
    margin: 20px;
    padding: 20px;
    border: 5px dashed #789;
}
.pos-element{
    position: absolute;
    width: 70px;
    height: 70px;
    margin: 10px;
    padding: 20px;
    border: 5px dashed #a74;
    background: #e5c5a5;
    left: 0;
    top: 0;
}
           

得到的結果是:

“位置”在css裡的細節

由于邊框是明顯的有視覺效果的部分,是以border edge的位置很容易确定。注意兩個元素都有完整的内外邊距和邊框,而此時

div.pos-element

的坐标是(0, 0),圖中間距為10px。經過分析可以得知,這個10px間距來自

div.pos-element

margin

。是以可以得到什麼結論呢?結論如下:

“位置”在css裡的細節

首先,網頁的平面坐标系和通常的數學平面直角坐标系不同,y軸的正方向是朝下的。事實上,包括Photoshop、Flash等平面設計在内的界面,都使用這樣的坐标系。

這種搭配的情況下,構成坐标系xy軸的是用作容器的元素的padding edge。其中padding edge的左上角即為坐标系的原點。

絕對定位的元素設定的

left

top

所形成的坐标位置點,位于該元素的margin edge的左上角。也就是,定位元素是用margin edge的左上角這個點來對齊坐标的。

此外,構成定位參考坐标系的是包含塊,而不一定是直接父元素。

包含塊

包含塊(containing block)是css規範的視覺格式化模型(visual formatting model)中的概念,它是一個邏輯的矩形框,用于css的定位及尺寸的計算,并不限制内部元素的位置,可以溢出。它和盒模型的關系可以這樣描述:一個DOM元素對應一個盒模型,盒模型的某一條邊(這不是固定的)将标明該DOM元素建立的包含塊範圍。

一般情況下,一個DOM元素的子元素(以及這些子元素的盒模型)以這個DOM元素的content edge作為包含塊的邊界。

特别地,在這種

position: relative;

的父元素及

position: absolute;

的子元素的搭配中,絕對定位的子元素以父元素的padding edge作為自己的包含塊的邊界。

更具體的包含塊的邊界判定可以參考W3C的說明。

普通流、浮動與絕對定位的三方糾葛

在确定一個DOM元素的位置時,我們需要考慮它的定位方案(positioning schemes)。一個DOM元素可以選擇以下三個方案:

  • 普通流(normal flow)。如果你對這個元素什麼也沒幹,那就是它了。
  • 浮動(floats)。當元素的

    float

    屬性設定了

    left

    right

    後。
  • 絕對定位(absolute positioning)。當元素的

    position

    屬性設定了

    absolute

    fixed

    後。

如果一個DOM元素既設定了浮動又設定了絕對定位,那麼它是絕對定位(

float

會被重置為

none

的計算值)。

你也許聽說過“脫離文檔流”這樣的詞彙,它在css裡是确實存在的概念,原詞是out of flow。如果一個元素的定位方案是浮動或絕對定位,又或者這個元素是根元素(root element),就稱這個元素是脫離文檔流(out of flow)的。

文檔流的問題

同一層級的兄弟元素,同時有普通流、浮動、絕對定位這三種定位方案時,它們之間的互相關系是怎樣的?

要理清這個互相關系,需要了解脫離文檔流具體是什麼意思。

css規範中這樣描述絕對定位:

In the absolute positioning model, a box is removed from the normal flow entirely and assigned a position with respect to a containing block.

請注意這裡的entirely,這是在說,絕對定位是完全脫離文檔流的。為什麼要強調完全呢?

因為,脫離文檔流是一個比較暧昧的概念,還有不完全的。請看浮動的描述:

In the float model, a box is first laid out according to the normal flow, then taken out of the flow and shifted to the left or right as far as possible.

前文已經說過,浮動被歸類為out of flow,也就是脫離文檔流,但這裡卻提到了先基于文檔流取得一次位置,然後再向左或向右移動。是以,浮動不是完全脫離文檔流的。

浮動的特性

之前看到過别人的這樣一個提問:

“位置”在css裡的細節

對應html代碼(css省略):

<div class="scheme-container">
    <div class="scheme-element-normal">normal</div>
    <div class="scheme-element-float">float</div>
</div>
           

按照傳統的“浮動元素是脫離文檔流的”的了解,為什麼這個右浮動元素隻是浮動到了它所在行的右邊,而不是整個容器的右上角呢?

答案就是,浮動不是完全脫離文檔流的。這有兩方面的意思。

一方面,浮動可以影響文檔流。你一定見過把一段文字裡的某一個圖檔浮動,來制造文字環繞圖檔效果的用法:

“位置”在css裡的細節

這些位于文檔流内的文字,仍然會為浮動元素留出空間,而并非互不相幹。這其實是浮動元素影響行框(line box)的寬度的結果。

另一方面,浮動會受到文檔流的影響。規範裡列出的浮動元素的精确特性規則中有這樣一條:

The outer top of a floating box may not be higher than the outer top of any block or floated box generated by an element earlier in the source document.

這裡的outer top就是margin edge (outer edge)的top edge。意思是,浮動元素不可以高于任何在源文檔之前的塊元素或浮動元素。我們很熟悉浮動元素是會一個接一個地尋找空間排列的,但這一條卻告訴我們,如果前面還有塊元素,那麼它們也會影響浮動元素的上邊緣位置。

請看這個例子:

“位置”在css裡的細節

如果一個浮動元素之前還有其他的浮動元素,那麼會考慮到它們,而可能排列到一個更靠下的位置。那麼,如果之前還有塊元素,而且占據了更大的位置呢?從上圖可以看到,浮動元素同樣會将其考慮在内。

這就是前面的提問的詳細解釋了。

絕對定位

絕對定位大部分時候并不像浮動這樣讓人困惑。顯然,它被定義為“完全脫離文檔流”,就是說它和文檔流的關聯很弱,一般隻根據包含塊和坐标來确定位置就可以了。

完全脫離文檔流,也仍然不是說和文檔流毫不相幹,各自為政。它的意思是:這個元素在參與布局時,不會影響在它之後的兄弟元素。絕對定位元素,在

left

top

等定位屬性取值

auto

的時候,仍然會根據文檔流取得一個可用的計算值。

三者的覆寫關系

如果有重疊,就要考慮覆寫關系了。它們的位置從下到上依次是:

  • z-index

    為負的定位元素。
  • 普通流(normal flow)的非行内元素。
  • 浮動元素。
  • 普通流的行内元素。
  • z-index

    auto

    或 的定位元素。
  • z-index

    為正值的定位元素。

結語

想要讓元素穩定地待在為它預定的位置上,還是有很多功課要做的。本文隻介紹了一部分有關位置的細節知識,如果你也曾對這些内容有所困惑,那麼希望能有所幫助。

(重新編輯自我的部落格,原文位址:http://acgtofe.com/posts/2015/10/xyz-in-css)

繼續閱讀