前言


一開始, 你或許會以為我瘋了: 沒事幹嘛硬要用 flex 來做原本 <table> 做的事. 但是如果你想要弄一個可以自動依畫面寬度 (或設備的寬度) 彈性調整的表格模組; 或者你也寫過微信小程序, 那你肯定就不會那麼說了. 以微信小程序來說: <table> <tr> <th> <td> ... 這些 HTML tag 在小程序中都無法正常展現在畫面上. 唯一可用的就只有一種 tag 叫做 <view>. 即使我們強制把這些 HTML tag 的 CSS 設成:

table   { display: table }
tr      { display: table-row }
th, td  { display: table-cell }

也只能展現原本的沒有 rowspancolspan 的狀況. 無奈之下, 我們只好死馬當活馬醫.

結果, 還真的可以啊... 而且不用多想, 沒有什麼大不了的設定: 就是用 flex-direction:rowflex-direction:column 二者交替套用就是了.

簡單狀況


我們先從比較簡單的狀況來: 沒有行合併也沒有列合併, 或是二者不同時出現的狀況下, 如何用 flex 來表現 table.

在即沒有行合併也沒有列合併的情況下, 我們可以有二種作法: 如下二張圖片(註一).

col-first.png

先分為垂直行, 再分為水平列.

row-first.png

先分為水平列, 再分為垂直行.

這二種方法各自適合一種行或列的合併.

row-merge.png

列合併 rowspan

col-merge.png

行合併 colspan

註一: 這裡用的是台灣的習慣: 直行橫列, 不習慣的人就麻煩稍微忍耐一下.

  • 第一種方法 (先分為垂直行, 再分為水平列)(註二):
    一般表格都是垂直欄寬度不一. 這種方法對於這類表格是特別方便: 只要在第一層 flex 容器內容項設好寬度即可; 不過相反的, 如果每一列的高度都不相同, 需要遂一調整列高的話它就不方便了. 同時這種方法也可以處理有列合併 (rowspan) 的情況: 直接修改需要列合併的格子的高度即可. 當然要合併二列, 三列..., 或者不只一處都是可以的.
  • 第二種方法 (先分為水平列, 再分為垂直行)(註二):
    這個方法比較接近原本 <table> <tr> <td> 的寫法, HTML 原始碼看起來可能比較習慣, 使用程式產生這樣的表格也相對容易. 但和第一種方法相反的, 要在每一個第二層 flex 容器內容項都附加上它的欄寬設定. 在表格資料眾多時, 人工處理會相當的麻煩. 好在使用程式處理資料時可以一併附加上欄寬設定, 煩人的程度也就不那麼明顯. 同樣的, 它也可以處理有行合併 (colspan) 的情況: 直接修改需要行合併的格子的寬度即可. 一樣的要合併二行, 三行..., 或者不只一處都是可以的.

註二: 先切分為垂直行時, 內容項要水平方向排列, 所以要將第一層 flex 容器設為 flex-direction: row. 反之, 先切分為水平列時, 內容項要垂直方向排列, 所以要將第一層 flex 容器設為 flex-direction: column.

在設定表格欄寬時, 有二種策略可供運用: 一種是以寛度為 class name; 一種是以欄位順序為 class name. 以寛度為 class name 在欄位不多時比較方便, 以欄位順序為 class name 順序則比較方便微調欄寬. 這二種策略各有利弊, 但是共通的就是調整上都很煩. 另外我還會單獨設定一個 CSS class 為 flex-grow:1, 再把我認為可以延伸寬度的欄加上這個 class name.

有一點要注意的是: 欄寬和列高的設定是必要的, 我們就是經由這二者的設定讓 flex 展示出類似表格的外觀. 另外, 預設的 flex-shrink:1 在寬度不足時, 會使整個表格的外觀出現欄寬參差不齊的問題. 建議將它改為 flex-shrink:0, 並同時設定表格的最小欄寬度和超長時的處理方法.

設定合併格子的高度或寬度時, 還有個投機取巧的方法: 把它設成flex-grow:1就可以了. 當然, 這個只適用在其他格子的高度和寬度都有明確指定, 而且只有一個合併格子時.

接下來, 看實際應用的例子. 首先是使用第一個方法的例子.

See the Pen Rendering a table by flex (part2) by Jack Ting (@magicjack) on CodePen.

CSS Tips:

  • 不設 background-color (透明) 和設 background-color: white 在需要收縮 (flex-shrink) 時二者的效果不同.
  • 為何連 .th, .td 也是用 flex? 因為用 flex 將格子中的內容上下左右都居中比較容易.
  • .th, .td 裡的 text-align:center 是用來將列合併的格子的內容文字居中.

然後是第二個方法的實例: 這段 CSS 就是我在微信小程序裡共用的 .wxss 檔的內容. 這段 CSS 它主要是應用於展現表格資料, 所以用的是先分為水平列, 再分為垂直行這種和 table 比較類似的寫法. 為了方便在 codepen 上展示, 我也貼了一段 HTML 並且把原本 .wxml 檔裡的 <view> 都改成了 <div>.

See the Pen Rendering a table like by flex by Jack Ting (@magicjack) on CodePen.

複雜狀況


簡單的狀況會了, 接著就是表格中同時有行合併列合併的情況了. 一開始因為有點時間壓力, 沒有太多時間可以思考這個問題, 所以就被卡在這裡. 幸好, 隔一個晚上就想通了, 想想也沒有什麼大不了的: 把上面說的二個方法重複套用就可以了. 重點在先把有行合併的部份當成各當成一行, 有列合併的部份也各當成一列來進行切分. 之後再將剛才避開的合併行和合併列的部份, 當成另一個獨立表格, 再進行一次切分處理. 不過經過這樣子處理, 表格中的資料在 HTML 檔中已經不像使用 <table> 時那樣井然有序了.

我們直接看下面的圖片, 應該比較容易理解. 下圖這樣的表格滿常見的, 用 <table> 表現應該沒什麼難的.

tbl-org.png

原始表格

現在我們先將它分為水平列, 如下面左邊的圖片所示. 然後我們把每一列再各自切分為幾個垂直行. 接下來, 我們可以清楚看到需要繼續處理的部份: 第 1 列的第 4 行 (需要先分為水平列, 再分為垂直行, 一共二次切分)(註三), 第 4 列的第 2~6 行 (需要再進行一次水平列切分).

tbl-bkdn-row1.png

先分為 5 個水平列

tbl-bkdn-row2.png

每一列再各自分為幾個垂直行.

當然, 我們也可以先將它分為垂直行, 如下面左邊的圖片所示. 然後我們把每一行再各自切分為幾個水平列. 接下來, 我們可以清楚看到第 3 行的第 1~5 列都需要繼續處理.

tbl-bkdn-col1.png

先分為 3 個垂直行

tbl-bkdn-col2.png

每一行再各自分為幾個水平列.

在這個例子中, 大家應該都很清楚第二種分切法效率差了一些, 但是一樣可以完成我們想要的結果, 只是需要多幾個步驟, 比較麻煩. 但其他的例子就不一定是第二種分切法效率差, 要試了才會知道.

註三: 其實第二次切分時, 不一定要先分為水平列, 再分為垂直行. 例如下方實例所展示的: 我把 class .col 設定成 flex-flow: row wrap. 利用 flex-wrap: wrapflex-basis:100% 設定, 把橫排變成直排, 或是讓這三個表頭項目一次就排好.

See the Pen Rendering a table by flex (Complex2) by Jack Ting (@magicjack) on CodePen.

CSS Tips:

  • 請把欄寬統一設在第二層. 如果你在自己實作時把它改每一個的最底層 (th, td), 就會發現有些在後面的格子 1px 1px 的越徧差越多.

表格線


最後, 也是初學者厭煩的格子線的問題. 這個問題有許多種解法, 這裡就只貼個人常用的方式:

  1. 在第一層 flex 容器 (.tbl-col.tbl-row) 上設定表格的外框: 上/下擇一, 右/左也擇一. 每個格子 (th, td): 上/下選擇另一個, 右/左也選擇另一個. 例如: 第一層 flex 容器設定 border-topborder-right, 每個格子則設定 border-bottomborder-left
    這個方法在同時有行合併列合併時比較簡便一些.
  2. 在第一層 flex 容器 (.tbl-col.tbl-row) 上設定表格的外框.
    • 用先分為垂直行, 再分為水平列時: 非第一行垂直行 (.col:not(:first-child)) 設左方的格線, 或非最後一行垂直行 (.col:not(:last-child)) 設右方的格線; 非第一個格子 (.td:not(:first-child)) 設下方的格線, 或非最後一個格子 (.td:not(:last-child)) 設上方的格線.
    • 用先分為水平列, 再分為垂直行時: 非第一列水平列 (.row:not(:first-child)) 設下方的格線, 或非最後一列水平列 (.row:not(:last-child)) 設上方的格線; 非第一個格子 (.td:not(:first-child)) 設左方的格線, 或非最後一個格子 (.td:not(:last-child)) 設右方的格線.

    MagicJackTing 發表在 痞客邦 留言(0) 人氣()