2007年5月2日

CSS 速戰速決 - 談多欄式編排

由於 Blog 風盛行又加上一般使用者不可能任意更動 BSP 業者提供的 BLOG 版型,這使得 CSS 的使用廣為 Blogger 們所知,只不過相對的 Blogger 們也一直為 CSS 所苦。

Blogger 們在編排其個人的 Blog 首頁時,最常搞不定的問題之一就是側邊欄 (sidebar) 暴走的情形。本來應該出現在頁面某側的側邊欄卻莫名其妙的出現在本文區的上/下方或其他位置的情形屢見不鮮,多數的 Blogger 們在遇到此問題時通常會選擇妥協,畢竟與其盲目的進行測試還不如把時間留下來寫寫文章、陪陪閃光。反正只要自己看了不礙眼,讀者看了不反彈的話就無須刻意放在心上讓自己難受。

網路上已有太多提到如何使用 CSS 去編排、設定兩欄或三欄式頁面的說明文件,但是因為近日才剛和 CSS 搏鬥過,為免日後遺忘還是不能免俗的要做個簡單的記錄。

首先,透過 CSS 對頁面進行排版只有兩個簡單的概念:

  1. 元素的尺寸大小:所謂元素的尺寸大小包含 寬度 (width)邊界 (margin)邊距 (padding)
  2. 頁面的排版流程:此處所指的排版流程係指 是否依序 將元素填滿頁面的程序。讓元素脫離頁面的標準排版流程有兩種方法:
    1. 浮動處理:透過指定 float 屬性迫使元素向左、右浮動靠齊。因為此法受限於元素出現順序影響,所以不在此文中討論。
    2. 直接定位:透過指定 position 屬性迫使元素固定顯示於特定位置,是本文的重頭戲。
範例檔 是個三欄式編排的基本結構,除了方便標示各區塊的位置及大小所做背景色設定外,基本上算是一個沒有任何排版效果的純 HTML 檔。此文將嘗試透過這個範例檔產生三種不同的頁面版型(三種版型都是固定側邊欄寬度,本文區可隨瀏覽器寬度自動調整的彈性編排):
  1. 雙欄,側邊欄靠左,本文區靠右
  2. 雙欄,側邊欄靠右,本文區靠左
  3. 三欄,側邊欄靠兩側,本文區置中

雙欄,側邊欄靠左,本文區靠右

需求假設

版面編排假設如下:
  1. 隱藏側邊欄二,僅顯示側邊欄一
  2. 側邊欄一寬度 200px,向左貼齊欄框
  3. 本文區與側邊欄間距 10px
  4. 其他空間保留給本文區顯示

實作設定

隱藏側邊欄二,僅顯示側邊欄一

#sidebar2 {
  border-width: 1px;
  border-style: dotted;
  border-color: yellow;
  background-color: gray;

/* 將側邊欄二藏起來 */ display: none; }

側邊欄一寬度 200px,向左貼齊欄框

#sidebar1 {
  border-width: 1px;
  border-style: dotted;
  border-color: red;
  background-color: Lime;

/* 寬度 200px */ width: 200px; /* 絕對定位 */ position: absolute; /* 與父元素左邊界距離 0 */ left: 0; /* 與父元素上邊界距離 0 */ top: 0; }

父容器元素的配合

#container {
  text-align: left;
  background-color: Menu;
  border-width: 1px;
  border-style: solid;

/* 配合 sidebar1 定位所需,其父元素容器也必須使用定位模式 */ position: relative; }

所謂的相對定位模式是指以目前元素排版的參考點為基準進行指定位移的定位。舉個例子來說,若瀏覽器已編排過部份網頁元素,而目前的排版參考點停在父容器元素座標 (100, 35) 的位置上,那麼:

  1. position: absolute; left: 10; top; 10; 表示由父容器元素座標 (0 + 10, 0 + 10) 開始編排元素的內容;
  2. position: relative; left: 10; top; 10; 表示由父容器元素座標 (100 + 10, 35 + 10) 處開始編排元素的內容。
本文區的設定

目前本文區的排版基準點仍然是父容器元素座標 (0, 0) 處,所以目前本文區中的部分內容被側邊欄所遮蓋。只要將本文區的左邊界往右移 210px ( 側邊欄寬 200px + 與本文區 10px 間隔 ) 後即可。

#content {
  border-width: 1px;
  border-style: dashed;
  border-color: blue;
  background-color: InfoBackground;

/* 設定左邊與與父容器元素相距 200 (#sidebar1 寬度) + 10 (需求限制) px */ margin-left: 210px; }

修正 Internet Explorer 6 Bug

前述的設定看起來很完美(在 Firefox 及 Opera 上的確很完美),不過如果把它交給 IE6 去處理後就不是這麼回事了。你會發現整個側邊欄會很莫名其妙的隨著本文區的 margin-left 設定而黏死在本文區左邊界上。這是 IE6 特有的問題,所以應該是個定位解釋錯誤的 Bug。

顯示錯誤的問題似乎出在父容器元素 #container 上的定位 (position) 設定,如果將定位模式由相對定位 (relative) 改成絕對定位 (absolute) 後,IE6 的顯示就會正常,不過此時也會造成 Firefox/Opera 等的些微異常( #container 左、右邊界寬度不同,而且 Opera 更明顯)。

為了解決這個問題只好先解決 IE6 的 Bug 後,再另外對 Firefox/Opera 設定正確的定位方式。關鍵的作法就是透過一個 Firefox/Opera 看的懂但 IE6 看不明白的 CSS 選擇器 ( CSS Selector ) 讓 Firefox/Opera 得以重設正確的定位方式。

※此 Bug 可能連 IE7 都有。不過因為現行環境 ( Windows 2000 / Linux ) 上無法直接安裝,所以這是在 Linux 上透過 WINE / IES4LINUX 安裝 IE7 render engine 做測試所得。

#container {
  text-align: left;
  background-color: Menu;
  border-width: 1px;
  border-style: solid;

margin: 0; /* 配合 sidebar1 定位所需,其父元素容器也必須使用定位模式 */ /* 使用 絕對定位 模式以配合 IE6 的 Bug */ position: absolute; }

/* 使用 > 選擇器避免 IE6 的誤會 */ body > #container { /* 給 Fireofx/Opera 使用的定位設定 */ position: relative; }

CSS 選擇器 ( > ) 的意思是子代選擇器,它的成立條件僅限於父元素的第一代子元素有效,本例中 #container 是 body 元素的第一代子元素,因此條件成立且不論是 Firefox、Opera 還是 IE6 都能正確顯示。

完整範例請見 CSS 測試一 - 雙欄,側邊欄在左,使用整個頁寬做顯示

雙欄,側邊欄靠右,本文區靠左

需求假設

版面編排假設如下:
  1. 隱藏側邊欄二,僅顯示側邊欄一
  2. 使用頁面寬度的 90% 為顯示區,顯示區必須左右置中
  3. 側邊欄寬度 200px,向右貼齊欄框
  4. 本文區與側邊欄間距 10px
  5. 其他空間保留給本文區顯示

實作設定

隱藏側邊欄二,僅顯示側邊欄一

#sidebar2 {
  border-width: 1px;
  border-style: dotted;
  border-color: yellow;
  background-color: gray;

/* 將側邊欄二藏起來 */ display: none; }

頁面寬度 90% 為顯示區,顯示區必須左右置中

#container {
  text-align: left;
  background-color: Menu;
  border-width: 1px;
  border-style: solid;

/* 使用頁面寬度 90% 為顯示區 */ width: 90%; /* 左右置中 */ margin: 0 auto; /* 配合 #sidebar1 定位設定 */ position: relative; }

修正 Internet Explorer 6 Bug

同樣的,前述的設定在 IE6 上會發現無法顯示區置中的情形,原因是 margin: 0 auto; 這個設定無法生效。由於 IE6 誤將 text-align: center 解釋為所有子元素皆置中(應該不包含容器元素),為了瀏覽器相容性之故必須在 #container 的父容器元素 (body) 中加入 text-align 的設定。

/* 配合 IE6 修正區塊置中的錯誤 */
body {
  /* 文字編排置中 */
  text-align: center;
}

#container { text-align: left; background-color: Menu; border-width: 1px; border-style: solid;

/* 使用頁面寬度 90% 為顯示區 */ width: 90%; /* 左右置中 */ margin: 0 auto; /* 配合 #sidebar1 定位設定 */ position: relative; /* body 中的文字編排置中設定會繼續下來,所以必須將文字置中之設定改回左靠 */ text-align: left; }

側邊欄寬度 200px,向右貼齊欄框

#sidebar1 {
  border-width: 1px;
  border-style: dotted;
  border-color: red;
  background-color: Lime;

/* 側邊欄寬度 200 px */ width: 200px; /* 絕對定位在父容器元素的右側 */ position: absolute; /* 右邊界與父容器間之距離 0 */ right: 0; /* 上邊界與父容器間之距離 0 */ top: 0; }

本文區與側邊欄間距 10px

#content {
  border-width: 1px;
  border-style: dashed;
  border-color: blue;
  background-color: InfoBackground;

/* 距離右邊界 200 (#sidebar1 寬度) + 10 (需求限制) px */ margin-right: 210px; }

奇怪的是在這個例子中的 #sidebar1 並沒有像第一個例子中 position: absolute; 定位錯誤的問題。事實上如果想按照第一個例子中的設定去調整 #container 的定位模式的話,就會看到 #sidebar1 緊靠在瀏覽器的邊框上,這當然不符需求所示。

經過重覆測試之後確認應該是 width: 90%; 這個設定所造成的差異,這表示在 IE6 上父容器元素的 width 設定與否會造成定位模式的改變。在排版時若發現元素總是不依指示隨意暴走的話,也許朝這方面加以檢查。

完整範例請見 CSS 測試二 - 雙欄,側邊欄在右,使用 90% 頁寬做顯示區使用,如果範例一也要改成使用 90% 頁寬做顯示區使用的話請參考 CSS 測試一之一 - 雙欄,側邊欄在左,使用 90% 頁寬做顯示

三欄,側邊欄靠兩側,本文區置中

需求假設

版面編排假設如下:
  1. 使用頁面寬度的 90% 為顯示區,顯示區必須左右置中
  2. 側邊欄一寬度 200px,向右貼齊欄框
  3. 側邊欄二寬度 150px,向左貼齊欄框
  4. 本文區與側邊欄間皆相距 10px
  5. 其他空間保留給本文區顯示

實作設定

頁面寬度 90% 為顯示區,顯示區必須左右置中 , IE6 同樣有無法置中的問題,所以仿照第二個例子使用 text-align 設定。

/* 配合 IE6 修正區塊置中的錯誤 */
body {
  /* 文字編排置中 */
  text-align: center;
}

#container { text-align: left; background-color: Menu; border-width: 1px; border-style: solid;

/* 使用頁面寬度 90% 為顯示區 */ width: 90%; /* 左右置中 */ margin: 0 auto; /* 配合 #sidebar1 定位設定 */ position: relative; /* body 中的文字編排置中設定會繼續下來,所以必須將文字置中之設定改回左靠 */ text-align: left; }

側邊欄一寬度 200px,向右貼齊欄框

#sidebar1 {
  border-width: 1px;
  border-style: dotted;
  border-color: red;
  background-color: Lime;

/* 側邊欄寬度 200 px */ width: 200px; /* 絕對定位在父容器元素的右側 */ position: absolute; /* 右邊界與父容器間之距離 0 */ right: 0; /* 上邊界與父容器間之距離 0 */ top: 0; }

側邊欄二寬度 150px,向左貼齊欄框

#sidebar2 {
  border-width: 1px;
  border-style: dotted;
  border-color: yellow;
  background-color: gray;

/* 側邊欄寬度 150 px */ width: 150; /* 絕對定位在父容器元素的左側 */ position: absolute; /* 左邊界與父容器間之距離 */ left: 0; /* 上邊界與父容器間之距離 */ top: 0; }

本文區與側邊欄間皆相距 10px

結合範例一、二,分別設定本文區的左、右邊界即可。

#content {
  border-width: 1px;
  border-style: dashed;
  border-color: blue;
  background-color: InfoBackground;

/* 距離右邊界 200 (#sidebar1 寬度) + 10 (需求限制) px */ margin-right: 210px; /* 距離左邊界 150 (#sidebar2 寬度) + 10 (需求限制) px */ margin-left: 160px; }

完整範例見 CSS 測試三 - 三欄,側邊欄在兩側,使用頁寬 90% 為顯示區

影響頁面編排的因素

最主要影響編排結果的因素在於 IE6 與 W3C 相容的瀏覽器有不同的 BOX MODEL 特性。簡單而言,當我們在 CSS 中指定了某元素的寬度後,符合 W3C 標準的瀏覽器生成的元素實際佔用空間 (Visible Area) 為 border-left-width + padding-left + width + padding-right + border-right-width,但是 IE6 生成的元素實際佔用空間 (Visible Area) 卻等於 width 之值,這就可能造成同樣的設定在不同的瀏覽器中有不同的尺寸大小(差異在 border 及 padding 之值)。

如果只是尺寸大小的不同倒也還好,最怕的是因此造成側邊欄的暴走。如果出現側邊欄暴走的情形時,請優先檢討 border 及 padding 的設定值,很多情形下將這兩個值都設為 0 時即可讓暴走的情形消失,雖然此法經常有礙視覺效果。

CSS 排版利器

目前最方便且不用花費任何一毛錢的網頁開發組合是 Firefox + WebDeveloper + FireBug,但此組合下所設定出來的 CSS 版面設定通常與 IE 不太相容。然而基本上我會建議以此組合為編排的主要工具,然後再回到 IE 下檢視有無異常。

  1. FireBug 可以線上檢視特定元素的 CSS 設定繼承情形,也能動態修改 CSS 的結果。相對於 WebDeveloper 一個個 CSS 檔分別顯示的作法來看,這是追蹤 CSS 的最佳利器。
  2. WebDeveloper 可以直接載入一個自訂的 CSS 檔以便替換掉網頁上原本的 CSS 設定,對於驗證 CSS 設定的正確性有不可計量的優點。
在 IE 部份微軟有提供類似 Web Developer 功能的 IE Developer Toolbar(目前版號 1.00.2188.0 ),不過功能上遜色太多,比較適合用來追蹤 DOM 結構及查看 CSS 套用結果。另外也有商業版本的 IE 外掛套件可以選擇,我看到的價格一套要 US$7x 算是很貴的。

至於 Opera 嘛… 有個 Menu Toolbar 啥的,可惜我無法啟用!