Markdown の Table 記法を CSS で実現する
Intro
本ブログは Markdown で原稿を書き、それを HTML に変換して表示している。このとき、CSS を用いて Markdown のシンタックスに似せた Style を適用している。例えば以下のように h2::before に content: '##' を指定するといった具合だ。
しかし、これまで <table> だけはうまく Markdown 記法を再現する CSS が書けないでいた。
そこで、周りの CSS 強者に実現できないか聞いてみたところ、@shqld, @araya, @yoshiko 達の協力を得て、かなりの完成度にすることができた。実現方法を記録する。
Before
実現したいのは以下のような記法だ。
| file type | size | ratio |
|:----------|-----:|------:|
| .webp | 9474 | 100% |
| .webp.gz | 2609 | 28% |
| .webp.br | 2544 | 27% |
特に <thead>, <tbody> の間に入るセパレータ (|:---|---:|---:|) の部分の長さを内容に合わせるためには <td> 内の文字数を CSS 内で取得する必要がある。これは JS 無しには難しそうだった。JS は使いたくないし、もし使うしかないなら Houdini の Paint API などで実装したい。しかし、Paint API も当時は Text Rendering がサポートされてなかったため、それも諦めていた。
などと考えている間に飽きて、適当に border を dash にしただけのお粗末な実装で誤魔化していた。
Call for Implementation
Markdown のパーサを書きなおした 際にこの作業を思い出し、誰か実現できる人がいたりしないか CSS に強そうなエンジニア何人かに聞いてみたところ、いくつかアドバイスをもらえたり、PoC を返してもらえた。URL のあるものだけ貼っておく。
これらを参考にさせてもらうことで、かなりの完成度まで実現することができた。
実装方針
HTML
まず、自作のパーサでは以下のような HTML が生成される。Align は HTML の align 要素が Deprecated だったため、class で実装している。
<table>
<thead>
<tr>
<th class=align-left>file type</th>
<th class=align-right>size</th>
<th class=align-right>ratio</th>
</tr>
</thead>
<tbody>
<tr>
<td class=align-left>.webp</td>
<td class=align-right>9474</td>
<td class=align-right>100%</td>
</tr>
<tr>
<td class=align-left>.webp.gz</td>
<td class=align-right>2609</td>
<td class=align-right>28%</td>
</tr>
<tr>
<td class=align-left>.webp.br</td>
<td class=align-right>2544</td>
<td class=align-right>27%</td>
</tr>
</tbody>
</table>
CSS
| は <th>, <td> の ::before / ::after の content でだいたい想像通りの実装を行う。
問題は <thead> / <tbody> の間の線だ。ここの実装は以下のように行っている。
- th の
::afterに-----------を長めに用意し、overflow hidden で切る - align が left なら
:--------にする - align が right なら direction を RTL にし
----------:にする - top と translateY で縦位置調整
これで見た目が以下のようになる。
スタイル自体はこれで完成だ。
Generated Content Model
デザインとしてのスタイル指定で、content に装飾用の文字を大量に指定した。本来はスタイルとして認識され、特に Crawler や Assistive Technology には無視されることが望ましい。
しかし、CSS Generated Content Module Level 3 の仕様では、以下のように定義されている。
Generated content should be searchable, selectable, and available to assistive technologies.
The 'content' property applies to speech and generated content must be rendered for speech output. [CSS3-SPEECH]
直後に ISSUE があるため、ここにはまだ議論と作業があるが、現状 CSS の Pseudo-elements に指定された content も Generated Content であるため、大量の - などがコンテンツとして認識される状況は望ましいものではない。
実際に Mac の Voice Over で検証したところ、確かに そこで、1.2. Alternative Text for Accessibility に定義されているように Alternative Text を以下の用に指定した。
これで Chrome などのブラウザでは、Voice Over での読み上げにスタイル部分が無視されるようになった。
しかし、Safari や Firefox は 通常、フォールバックとして Alternative Text 無しのものを先に指定することで見た目上は回避できる。
しかし、これでは Safari/Firefox を Voice Over で読み上げるような場合に、 せっかく表としてマークアップしているのに、スタイルのために読みにくい状況になるのは不本意なため、Fallback の指定は辞め、Alternative Text 未対応のブラウザには Table の Markdown Style を適用するのをやめることにした。
Markdown Style を Table に提供しない場合は、 そして、:---- の部分が「ハイフン N 個」のように、スタイル部分をコンテンツとして認識している。
Alternative Text
th::before,
td::before {
content: "|" / ""; /* 読み上げ等に対しては空文字として認識させる */
float: left;
}Fallback
content の Alternative Text に対応してないため、このままでは content そのものが無視される。
th::before,
td::before {
content: "|"; /* Safari 用のフォールバック */
content: "|" / ""; /* 読み上げ等に対しては空文字として認識させる */
float: left;
}content 部分が認識される。もし先頭に 1 文字つく程度ならまだ許容できるかもしれないが、今回は文字が多いため、煩わしさは他の記法とも異なるだろう。
Support query
| や - がなくなって罫線の無い Table になることを避けるため、以前自分が実装した border での実装をベースに提供することにした。
@supports を用いて、以下のように Alternative Text に対応している場合のみ、Markdown Style CSS を適用している。
/** default implementation */
@supports (content: "a" / "b") {
/** Markdown Style **/
}
figure / figcaption
<table> 自体が横に長い場合があるため、overflow を指定する必要がある。しかし、overflow を指定するためだけの親要素として <div> を追加するのがなんとなく気に食わなかったため、以前から TODO だった表のキャプションを導入することにした。
パーサとジェネレータは自作しているため、 Markdown は Caption の記法を標準で持たないため、様々な実装が独自の拡張として実装している。 せっかくなので、論文のようにキャプションの前に "表 1:" のようなプレフィックスを CSS Counter を用いて付与し、以下のように表示するようにした。
<table> の場合は <caption> を使うのが一般的ではあるが、より汎用的な仕様である <figure> と <figcaption> を用いることも仕様上可能だ(ただし両方の併用は不可)。そこで、<table> 全体を <figure> で囲み、そこに overflow を指定しつつ、<figcaption> を入れれば、全てが一度に解決するためこの方法を取ることにした。
Caption 記法
<figure> で囲むのは簡単だが、問題は <figcaption> の内容を Markdown 上どう表現するかだ。
[caption] のように括弧で囲うタイプのものが多いが、[] に独自の意味を持つ実装は割と多いため、未対応の場合は単なる <p> として扱われるよう、Caption: caption という記法で Table の直前に置くことにした。
Caption: Webp を gz/br 圧縮した結果
| file type | size | ratio |
|:----------|-----:|------:|
| .webp | 9474 | 100% |
| .webp.gz | 2609 | 28% |
| .webp.br | 2544 | 27% |
After
以上を踏まえて実装した表が以下だ。Chrome などでは Markdown Style で表示され、Safari / Firefox などでは従来の border style で表示される。
file type
size
ratio
.webp
9474
100%
.webp.gz
2609
28%
.webp.br
2544
27%
TODO
今回は text-overflow を clip にしているため、長さによっては横線の - が途中で切れる場合がある。
対策として text-overflow に "-" や ":" などを指定できると終端を綺麗に処理できそうだが、Firefox しかサポートしておらず、Firefox は上述の Alternative Text に対応してないため実現できなかった。
text-overflow と Alternative Text の compat が上がったら再検証したい。
DEMO
単体で動作するデモを以下に用意した。
謝辞
Reference
-
Spec
- CSS Generated Content Module Level 3
- CSS Conditional Rules Module Level 3
- Explainer
- Requirements Doc
- Mozilla Standard Position
- Webkit Position
- TAG Design Review
- Intents
- Chrome Platform Status
- WPT (Web Platform Test)
- DEMO
- Blog
- Presentation
- Issues
-
Other
- content - CSS | MDN
- @supports - CSS | MDN