今天要解的問題

在寫整理五張咖啡杯測表的差異時,我發現「總覽對照」那張表在桌機 960px 的內容欄裡只占了一半左右,右邊空一大片;手機上中文儲存格又不會斷行。而且為了讓它在手機 375px 不要橫向捲動,我還把儲存格用 <br> 切成很短的幾行,結果桌機上就更窄了。

但這不只是那一篇的事。把 kiwi-walk 上 50 篇有表格的文章、共 101 張表都量過一遍:

  • 1280px 寬時,有 79 張表不到內容欄寬的九成。
  • 375px 寬時,有 44 張表要橫向捲動。

杯測文章「總覽對照」表在 1280px 的改前改後:改前只占欄寬一半,改後拉滿整欄

技術背景

原因在 PaperMod 對所有 table 下的規則。reset.css 裡有兩條:

/* themes/PaperMod/assets/css/core/reset.css */
/* ... */
section,
table {
    display: block;
}
/* ... */
table {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    overflow-x: auto;
    word-break: keep-all;
}
  • display: block:外框是 100% 寬,但裡面的表只撐到內容的寬度,不會拉滿。
  • word-break: keep-all:中文字和字之間不能斷行。

另外 post-single.css 給每個儲存格一個最小寬度:

/* themes/PaperMod/assets/css/common/post-single.css */
.post-content table th,
.post-content table:not(.highlighttable, .highlight table, .gist .highlight) td {
    min-width: 80px;
    padding: 8px 5px;
    /* ... */
}

加上左右各 5px 的 padding,5 欄就是 5 × (80 + 10) = 450px,比手機上 347px 的內容欄還寬,所以 600px 以下要把它歸零。

想法與取捨

一開始我想要的是這幾點:

  • 桌機:文章表格拉滿內容欄寬。
  • 手機:中文儲存格自然斷行,375px 不橫向捲動
  • 改在 assets/css/extended/custom.css,不動主題 submodule。
  • 不影響程式碼區塊的表格(.highlight table、.highlighttable)和 stack-table shortcode。
  • 改了之後,杯測那篇的 <br> 可以考慮拿掉。

比較方式

先用 hugo 建好整個站,再用 Playwright 開那 50 篇文章,量每張表在 375px 和 1280px 的寬度。接著把候選的 CSS 直接注入建好的站,再量一次、截圖,拿真的頁面來比,而不是只畫 mockup。

塞不下的表

拉滿、斷行之後,手機上還是有表塞不進 347px。這時有三個做法:

  1. A:純 CSS,其中 4 張表改用 stack-table,不留任何橫捲。
  2. B:每張表外包可橫捲的框,不改文章,12 張表在手機上照舊橫捲。
  3. C:A 再加可橫捲的框當保險。

Claude 建議 A,我選了 B。

所以代價是:有 12 張表在手機上還是要在框內捲,「手機上不能橫向捲動」這條規則也多了一個例外。

杯測表的 <br>

Claude 原本建議保留,理由是那張表一格裝了兩個欄位,<br> 是在分欄位。

我選了這次一起改寫那張表,改成「6.00–9.75(0.25 一格)/無,直接加總」這種寫法,維持三欄。

實作

每張表包一層框

新增 layouts/_default/_markup/render-table.html:

{{/* ... */}}
<div class="table-wrap">
<table
  {{- range $k, $v := .Attributes }}
    {{- if $v }}
      {{- printf " %s=%q" $k $v | safeHTMLAttr }}
    {{- end }}
  {{- end }}>
  {{/* ... */}}
  <tbody>
    {{- range .TBody }}
    <tr>
      {{- range . }}
      <td
        {{- with .Alignment }}
          {{- printf " style=%q" (printf "text-align: %s" .) | safeHTMLAttr }}
        {{- end -}}
      >{{- .Text -}}</td>
      {{- end }}
    </tr>
    {{- end }}
  </tbody>
</table>
</div>
{{- /**/ -}}

裡面照 Hugo table render hook 的範本輸出屬性、表頭、表身和每格的對齊,所以每張 Markdown 表外面都多了一層 <div class="table-wrap">。

覆寫表格規則

assets/css/extended/custom.css 檔尾加一段:

/* ... */
.post-content .table-wrap {
    overflow-x: auto;
    margin-bottom: var(--content-gap);
}
.post-content table:not(.highlighttable, .highlight table, .gist .highlight) {
    display: table;
    width: 100%;
    word-break: normal;
    overflow-wrap: break-word;
}
.post-content .table-wrap > table {
    margin-bottom: 0;
}
@media (max-width: 600px) {
    /* PaperMod's 80px cell minimum alone makes 4+ columns wider than 375px. */
    .post-content table:not(.highlighttable, .highlight table, .gist .highlight) :is(th, td) {
        min-width: 0;
    }
}

每條對應 PaperMod 的一條規則:

  • display: table; width: 100%:取代 display: block,表格拉滿欄寬。
  • word-break: normal:取代 keep-all,中文可以斷行;overflow-wrap: break-word 讓真的放不下的字才斷開。
  • 600px 以下 min-width: 0:拿掉 80px 的儲存格下限。
  • .table-wrap 的 overflow-x: auto:還是塞不下的表,只在框內捲。

:not() 沿用 PaperMod 同一組排除,程式碼區塊的表照舊。

stack-table 的選擇器

同一個檔案裡,stack-table 手機版那條也加上同一組 :not(),原因寫在下面踩到的坑:

-    .post-content .stack-table table {
+    .post-content .stack-table table:not(.highlighttable, .highlight table, .gist .highlight) {

杯測表

「總覽對照」的 <br> 換成括號和全形斜線,例如:

-| A. SCA 2004 | 6.00–9.75<br>(0.25 一格)<br>無,直接加總 | 100<br>80 |
+| A. SCA 2004 | 6.00–9.75(0.25 一格)/無,直接加總 | 100/80 |

最後在專案的寫作規則裡記下:文章表格是 <pre> 之外,第二個允許在框內橫捲的元件,新文章還是盡量讓表在 375px 塞得下。

踩到的坑

只改 word-break

只把 word-break 改成 normal,手機的捲動幾乎都消失(44 → 1)。但桌機的 79 張窄表一張都沒變,因為 display: block 還在。

anywhere 斷字

把 overflow-wrap: anywhere 套在所有儲存格,一張都不捲了,但連放得下的英文字也被從中間切開,例如 Supabas/e、Playwrig/ht、Assista/nt、14.9/4k。

anywhere 會把儲存格的最小寬度算成一個字,表格自動排版就把欄擠窄。改成 break-word 就不會。

code 要不要 anywhere

拿掉 code 的 anywhere,會多 5 張表撐破整頁(長的指令、路徑名稱)。但在可橫捲的框裡保留它,塞不下的表會先把程式碼欄擠成 1~2 個字寬(Heptabase 那張就是這樣)。

所以最後不給 code 設 anywhere,交給框去捲。

選擇器權重

新規則 .post-content table:not(.highlighttable, .highlight table, .gist .highlight) 的權重是 0,3,1(:not() 取裡面最重的 .gist .highlight),會蓋掉 stack-table 手機版的 display: block(0,2,1),About 的演講表就不堆疊了。

把 stack-table 那條也加上同一組 :not(),變成 0,4,1,演講表在手機上就又是一列一塊。

markdownify 的表

markdownify 也會走 table render hook,所以系列頁分組說明裡的表也被包了 .table-wrap。但那層 div 在 .post-content 外面,沒有任何樣式,量過寬高都和改前一樣。

小結

現在桌機上每張文章表格都拉滿欄寬,手機上中文會斷行,塞不下的 12 張只在自己的框裡捲,整頁不會捲。

量測(50 篇、101 張表)改前改後
1280px 表寬不到欄寬 90%790
375px 表在框內橫捲4412
375px 整頁橫捲00

最窄的表改前只有 240px(欄寬 960px);改後在 375px 最寬的那張(Heptabase「8.3 card」)撐到 765px,仍在框內捲。