Microdataのスコープを参考にしたCSS設計・命名規則
Microdataの構造を参考にしたCSS設計・命名規則、名付けて「S2CSS」を紹介します。
「S2CSS」は「プライマリスコープ」と「セカンダリスコープ」の2つのスコープクラス (範囲を示すclass属性値) からなるCSS設計・命名規則です。
Microdataとは?
HTMLは文章を「これは見出し」「それは段落」「あれは日時」といったように構造化します。
Microdataはそこから一歩踏み込み「この見出しは記事の題名」「その段落は記事の著者」「あの日時は記事の公開日」といったように、要素に与えられた具体的な意味を表します。
<article itemscope="itemscope" itemtype="https://schema.org/Article">
<header>
<h2 itemprop="name"><a … >記事の題名</a></h2>
<p itemprop="author" itemscope="itemscope" itemtype="https://schema.org/Person">
<img alt="著者の画像" … itemprop="image" />
<span itemprop="name">著者の名前</span>
</p>
<p><time itemprop="datePublished">2019-04-30</time></p>
<p><time itemprop="dateModified">2019-05-01</time></p>
</header>
…
</article>
Microdataは、スコープ内 (itemscope属性) にあるプロパティ (itemprop属性値) をタイプ (itemtype属性値) と組み合わせて具体的な意味を表します。
- [itemscope][itemtype$=”/Article”] + [itemprop=”name”] = 記事の題名
- [itemscope][itemtype$=”/Article”] + [itemprop=”datePublished”] = 記事の公開日
- [itemscope][itemtype$=”/Article”] + [itemprop=”dateModified”] = 記事の更新日
- [itemscope][itemtype$=”/Person”] + [itemprop=”image”] = 人物の画像
- [itemscope][itemtype$=”/Person”] + [itemprop=”name”] = 人物の名前
プロパティが別のタイプと組み合わさることで、より詳細な意味を表す場合もあります。
- [itemscope][itemtype$=”/Article”] + [itemprop=”author”][itemscope][itemtype$=”/Person”] + [itemprop=”image”] = 著者の画像
- [itemscope][itemtype$=”/Article”] + [itemprop=”author”][itemscope][itemtype$=”/Person”] + [itemprop=”name”] = 著者の名前
注目してもらいたいのがスコープです。
「Article」と組み合わさるプロパティは「Person」に含まれるプロパティを含みません。これはスコープによって分けられているためです。
このスコープという概念がCSSにはありません。あるのは祖先要素とその子孫要素といった要素の関係性のみです。
そのため、祖先要素に設定したスタイルが意図せず子孫要素に適用され、デザインやレイアウトが崩れるといった問題を引き起こします。
そこで、このMicrodataのスコープ、タイプ、プロパティという3つの概念を元にしたCSS設計・命名規則によって、長期運用可能な「壊れにくいCSS」を実現しようという訳です。
プライマリスコープについて
プライマリスコープは、Microdataであれば「タイプ (itemtype属性)」が設定された要素に指定するスコープクラスで、構造の1つのまとまりです。
他のクラスと区別するために、先頭にアンダーバーを付けます。
複数の単語からなる場合は、単語を “_alpha_beta” のようにアンダーバーで繋ぐスネークケースは使用しないで、単語を “_alpha-beta” のようにハイフンで繋ぐチェーンケースか、先頭以外の単語の頭文字を “_alphaBeta” のように大文字にするキャメルケースを使用します。
<article class="_article" itemscope="itemscope" itemtype="https://schema.org/Article">
<header>
<h2 itemprop="name"><a … >記事の題名</a></h2>
…
<p><time itemprop="datePublished">2019-04-30</time></p>
<p><time itemprop="dateModified">2019-05-01</time></p>
</header>
…
</article>
セカンダリスコープについて
セカンダリスコープは、Microdataであれば「プロパティ (itemprop属性)」が設定された要素に指定するスコープクラスです。
セカンダリスコープは “_alpha-beta_gamma-delta” のように先頭にプライマリスコープとアンダーバーを付けたチェーンケースかキャメルケースを使用します。結果、スネーク―ケースと、チェーンケースまたはキャメルケースが混在したような文字列になります。
<article class="_article" itemscope="itemscope" itemtype="https://schema.org/Article">
<header>
<h2 class="_article_name" itemprop="name"><a … >記事の題名</a></h2>
…
<p class="_article_date-published"><time itemprop="datePublished">2019-04-30</time></p>
<p class="_article_date-modified"><time itemprop="dateModified">2019-05-01</time></p>
</header>
…
</article>
CSSの記述ルール
2つのスコープクラスを基点としてセレクタを作ります。
/*
** article
*/
._article { … }
._article header { … }
._article_name { … }
._article_name a { … }
[class*="_article_date"] { … }
[class*="_article_date"] time { … }
._article_date-published { … }
._article_date-published time { … }
._article_date-modified { … }
._article_date-modified time { … }
アンダーバーはプライマリスコープの名称部分の前後にしか使用しないため、[class*="_article_date"]
のような属性セレクタが使えます。これにより、セレクタの詳細度を上げずに複数の設定が効率よく記述できます。
属性セレクタのこぼれ話
属性セレクタを活用するアイデアは、ウェブフォントのアイコンで有名な「Font Awesome」のCSSコードから着想を得ました。
「Font Awesome」でかつて使用されていたセレクタに [class^="fa-"], [class*=" fa-"]
があります。このセレクタでアイコンを表示するための共通設定をしていました。
fa-X といった値がclass属性の最初の値 (1文字目の前に半角スペースがない) なら [class^="fa-"]
が、2つ目以降の値 (1文字目の前に半角スペースがある) なら [class*=" fa-"]
が有効になります。
<i class="fa-X Y"></i><!-- [class^="fa-"] -->
<i class="W fa-X"></i><!-- [class*=" fa-"] -->
その後、fa で共通設定をして、アイコンや各種オプションを設定した fa-X を別途追加する、よくあるマルチクラスに改められました。
<i class="fa fa-X"></i>
このマルチクラスの欠点は、fa や fa-X 単体では全く機能しない点です。
そこで、考え出したのがアンダースコアから始まるclass属性値です。
1文字目にしかアンダースコアを使用しないと決めているのなら、最初か2つ目以降かを問わず、[class*="_fa-"]
だけで済みますし、共通設定のためのclass属性値も不要になります。
<i class="_fa-X"></i>
<i class="_fa-X _fa-Y"></i>
さらに、i:not([class*="_fa-"])
といった否定ができて便利です。
そもそも、なぜ2つのスコープクラスがあるのか?
次のHTMLコードはプライマリスコープだけ設定して、その他のクラスはプロパティと同じ値のクラスを設定したものです。
<article class="_article" itemscope="itemscope" itemtype="https://schema.org/Article">
<header>
<h2 class="name" itemprop="name"><a … >記事の題名</a></h2>
<p class="_author" itemprop="author" itemscope="itemscope" itemtype="https://schema.org/Person">
<img alt="著者の画像" … class="image" itemprop="image" />
<span class="name" itemprop="name">著者の名前</span>
</p>
<p class="date-published"><time itemprop="datePublished">2019-04-30</time></p>
<p class="date-modified"><time itemprop="dateModified">2019-05-01</time></p>
</header>
…
</article>
このHTMLコードに対して ._article .name { … }
といったスタイルを設定した場合「記事の題名」と「著者の名前」の2箇所に反映されます。
「記事の題名」の箇所にだけスタイルを反映させたい場合は、._article のスコープ内で ._author のスコープ外の .name を特定する必要があります。
現在のCSSでは、子セレクタを使って ._article > header > .name { … }
のように特定するしかありません。
こういった、あるスコープ内にあって他のスコープ内ではない要素を明確にするのがセカンダリスコープの役目です。裏を返すと、CSS側でプライマリスコープ内のクラスが重複しないなら、セカンダリスコープはいらないということでもあります。
プライマリスコープとセカンダリスコープのどちらにするか迷ったら
プライマリスコープを1つのモジュール (構成部品) として捉えましょう。例えば、他の箇所に使い回す部分ならプライマリスコープ、使い回さない部分ならセカンダリスコープといった感じです。
セカンダリスコープを無理して使う必要はありません。プライマリスコープの直下にプライマリスコープしかなくても問題はありません。プライマリスコープで適度に分割した方がメンテナンス性は高まります。
…
<style>
/*
** site
*/
._site { … }
/*
** header
*/
._header { … }
/*
** header-title
*/
._header-title { … }
._header-title a { … }
/*
** header-description
*/
._header-description { … }
/*
** page
*/
._page { … }
/*
** main
*/
._main { … }
/*
** side
*/
._side { … }
/*
** footer
*/
._footer { … }
/*
** footer-links
*/
._footer-links { … }
._footer-links li { … }
._footer-links a { … }
/*
** footer-copyright
*/
._footer-copyright { … }
</style>
…
<div class="_site">
<header class="_header">
<h1 class="_header-title"><a … > … </a></h1>
<p class="_header-description"> … </p>
</header>
<div class="_page">
<main class="_main">
…
</main>
<aside class="_side">
…
</aside>
</div>
<footer class="_footer">
<ul class="_footer-links">
<li><a … > … </a></li>
…
</ul>
<p class="_footer-copyright"> … </p>
</footer>
</div>
…
〆
いかがだったでしょうか?
「S2CSS」はMicrodataの構造を参考にしていますが、必ずしもMicrodataどおりである必要はありません。CSSやJSで管理しやすいようにスコープクラスを使い分けて、それぞれのウェブサイト・ウェブページにあったベストプラクティスを見つけてみてください。