あまり知られていないCSSの12の事実(続編)

1年以上前に、私は最初の12 Little-known CSS Facts(あまり知られていないCSSの12の事実)を発表しました。SitePointで最も人気の高い記事となりました。この記事を書いた後も、私はCSSのアドバイスやちょっとした情報の収集を続けました。だって、大ヒット映画も必ず続編を制作するじゃないですか。

12 Little-known CSS Facts: The Sequel
注釈
SitePoint/Natalia Balskaによるイラスト

それでは、早速今年も開発のヒントになる12の事実について話しましょう。もちろん、中にはもうすでにご存じのこともあると思いますが、この中で初めて知ったという事実がありましたら、コメントでお知らせください。

1. border-radiusプロパティに”スラッシュ”シンタックスを使用できる事実

このプロパティについてはSitePointに4年以上前に書いたのですが、この機能が存在することを、未だに多くの初心者や経験の豊富な開発者でさえ知らない人がいます。

まさかと思うでしょうが、次のborder-radiusコードは有効です。

.box {
  border-radius: 35px 25px 30px 20px / 35px 25px 15px 30px;
}

初めて見る方には分かりにくいと思いますので、仕様書の説明を確認してみましょう。

スラッシュで区切られて値が与えられた場合、スラッシュの前の値は縦半径を設定し、スラッシュの後の値は横半径を設定します。スラッシュがない場合は、両者の半径を等しく設定する。

次の画像も仕様書に掲載されています。

Multiple Radii on each corner with border-radius
画像の説明には、”border-top-left-radius: 55pt 25ptの2つの値は隅の曲率を定義する”と記載されています。

このようにスラッシュで値を区切ることで、非対称的に隅を丸めることができます。さらに詳しく知りたい場合は、先ほど紹介した記事を読むか、できたら、handy little interactive demo from MDN(役に立つMDNによるちょっとしたインタラクティブデモ)をお読みください。


ほとんどのborder-radiusジェネレータでは、このような値の設定はできません。私の知る限り、MDNのborder-radiusジェネレータでのみ設定が可能です。

2. 相対指定でもfont-weightプロパティに有効という事実

通常、font-weightプロパティの定義では、値はnormalboldのどちらかです。時には100 を一段階とする変数で指定されているのを目にすることがあると思います。100200など、最高で900までが指定できます。

しかし、忘れられがちな値が2つあります。bolderlighterです。

仕様書によると、この2つは継承値よりboldなウェイトあるいはlightなウェイトを指定します。ただの”bold”よりboldだったり、普通のテキストよりlightだったりする複数のウェイトを持つフォントを使用した時に最も効果を発揮します。

100を一段階とする値で、”bold”は700をマッピングし、”normal”は400をマッピングします。もし、ウェイトが300のフォントだった場合、継承値が400であれば、”lighter”の値は300を作ります。よりlightなウェイトがない(例えば、400が最もlightなウェイトだった)場合、値は400のままで、”lighter”の値は無効になります。

次のCodePenデモをご覧ください。

この例では、18種のスタイルを指定することが可能なExo 2という、フォントを使用しています。私のデモは、100を一段階としたウェイトにしているので、斜体ではないスタイルだけを埋め込んでいます。

デモに、”bolder”や”lighter”など、異なるfont-weightの値を持つネスト化した”box”要素が12個含まれていること分かると思います。これで、異なる継承値を持つテキストのウェイトにどう影響するか分かると思います。次はCSSコードの例になります。コード内の構文にお気づきたと思いますが、後続の”box”は前の要素にネスト化されていることを覚えておいてください。

.box {
  font-weight: 100;
}

.box-2 {
  font-weight: bolder; /* maps to 400 */
}

.box-3 {
  font-weight: bolder; /* maps to 700 */
}

.box-4 {
  font-weight: 400;
}

.box-5 {
  font-weight: bolder; /* maps to 700 */
}

.box-6 {
  font-weight: bolder; /* maps to 900 */
}

.box-7 {
  font-weight: 700;
}

.box-8 {
  font-weight: bolder; /* maps to 900 */
}

.box-9 {
  font-weight: bolder; /* maps to 900 */
}

.box-10 {
  font-weight: lighter; /* maps to 700 */
}

.box-11 {
  font-weight: lighter; /* maps to 400 */
}

.box-12 {
  font-weight: lighter; /* maps to 100 */
}

ここでは、”bolder”と”lighter”のキーワードは、100400700900の値にのみマッピングします。9種の異なるスタイルを持つこのキーワードでは、200300500600800の値にマッピングすることはありません。

このような結果になる理由は、ブラウザにコードの中から次に”bold”あるいは”light”なフォントを選択するよう指示しているからです。つまり、次に最もboldあるいは最もlightなフォントを選択しているのではなく、単に継承値よりboldあるいはよりlightなフォントを選択しているのです。しかしながら、最もlightなフォントの値が(例えばOpen Sansみたいに)300で、継承値が400の場合は、”lighter”の値は300をマッピングします。

初めは分かりにくいでしょうが、デモをいじっていくうちにこれらのキーワードの効果が分かってくると思います。

3. outline-offsetプロパティがある事実

デバッギングに役に立つため、outlineプロパティは広く知られています(ページの実行に影響はありません)。しかしながら、outline-offsetプロパティが仕様書に追加されていることはどうでしょう。名前どおりの動きをするこのプロパティは、要素からどれだけアウトラインをオフセットするのか定義します。

先ほどのデモでは、数値の範囲をスライダーで左右に動かせばアウトラインオフセットの変化を見ることができます。この例で使用している数値の範囲は0pxから30pxですが、CSSでは好きなだけ大きくすることが可能です。CSSのoutlineプロパティはショートハンドプロパティですが、outline-offsetプロパティは含まれていないため、別途outline-offsetを定義する必要があることに注意してください。

唯一の大きなデメリットは、outline-offsetプロパティがInternet Explorerで (IE 11でさえ)サポートされていないことです。他のブラウザではサポートされています。

4. table-layoutプロパティがある事実

いまさら、と思っているかもしれません。display: tableのことは全て知っている、簡単にページ中央を垂直にする方法、とも思っているかもしれません。しかし、私が話しているのはこのことではありません。私が話しているのは、table-layoutプロパティでdisplayプロパティではありません。

table-layoutプロパティはCSS機能の中でも説明が難しいので、まずは、仕様を見てから例を見てみましょう。仕様書の説明は次のとおりです。

この(高速)アルゴリズムを用いたテーブルの水平方向のレイアウトは、セルのコンテンツに依存しない。水平方向のレイアウトはテーブルの幅、列の幅、ボーダーおよびセルの間隔にのみ依存する。

恐らく、今までのW3C(World Wide Web Consortium)の仕様の中で初めて理解しにくいのではないでしょうか。冗談です(笑)。

しかし、いつものように例を見た方が分かりやすいのは本当です。次のデモでは、CSSでtable-layout: fixedをテーブルに追加しています。トグルボタンをクリックして、トグルを無効にしたり有効にしたりして比べてみてください。

この例を見れば、デフォルト設定にautoを使用する代わりに、table-layout: fixedを使用するメリットが分かると思います。必ずしも、最適な選択とは限りませんし、必要ないかもしれません。しかし、幅の異なるセルが混在する表を使用する上で覚えておくと便利だと思います。

昨年、Chris Coyierがこのプロパティについて分かりやすく説明していますので、さらに詳しく理解したい場合は、ぜひお読みください。

5. vertical-alignプロパティはテーブルのセルと他の要素とでは作用が異なる事実

2000年代中盤もしくはそれ以前からWebサイトのコーディングを行っていたり、HTMLメールを多く手掛けていたりすれば、おそらくどこかの時点でvertical-alignプロパティが従来のHTML4のvalign属性に対する標準的なアップグレードに相当していることにお気づきでしょう。これは現在のHTML5では廃止され、非準拠の機能とされています。

しかし、実はCSSのvertical-alignは異なる動きをします。テーブルの場合はまた異なります。おかしな話ですが、このプロパティがテーブルに対してまったく無効となるよりは、納得できる話です。

ではこのプロパティが通常の要素に適用される際、テーブルセルに適用される場合とはどのように異なるのでしょうか。

テーブルセルに適用されない場合、vertical-alignプロパティは以下の基本的なルールに準じます。

  • インライン要素、またはインラインブロック要素にのみ適用できる。
  • 要素の中身には影響しないが、代わりに、ほかのインライン要素またはインラインブロック要素との関連で要素自体の整列位置が変更される。
  • line-heightなどのテキスト/フォント設定や、隣接するインライン要素またはインラインブロック要素のサイズの影響を受ける。

次のデモをご確認ください。

vertical-alignプロパティはinput要素で定義されます。どれかのボタンを押すと、ボタンに表示されている値に変更できます。いずれもinputの位置を変更するものであることはお分かりですね。

これは、このプロパティと値を確認する非常に基本的な例です。さらに詳しく見てみたい方はChristopher Aueの2014年の記事を参照してください。

さて、これがテーブルに適用されるとなると、vertical-alignはまったく異なる動きをします。プロパティ/値を、1つまたは幾つかのテーブルセルに適用した場合、テーブルセルの中身は、選択された整列位置の影響を受けます。

今のデモでお見せしたように、テーブルセルに対しては4つの値のみが適用できます。baselineを押した場合にのみ、分割されたセルにも影響がありますが、基本的にはvertical-alignを適用したセルの中身の配置のみが影響を受けます。

6. ::first-letter 疑似要素は思っている以上に賢い事実

::first-letter疑似要素を使うと、指定した要素の第一文字目のスタイルを変更でき、印刷業界では長年常識とされているドロップキャップ効果が得られます。

これを使用する利点は、要素の”一文字目”を構成するものに対して、ブラウザが適切な水準を備えているということです。このことは、Matt Andrewsのツイートで初めて見ました。しかし、彼はこれを喜ばしいとは思っていないようでした。彼が挙げた例を以下のCodePenで見てみましょう。

私が見たところ、4大ブラウザはどれも同様の扱いをするようなので、これは正しい挙動だと考えられますし、素晴らしいです。開き括弧が”1文字目”として認識されているのはいささか妙ではありますが。”1キャラクタ目”と言った方がふさわしいかもしれませんし、これ自体まったく新しい疑似クラスになり得ると思います。

7. HTMLクラスリストに区切り文字として無効文字を使用できる事実

このコンセプトはBen Everardによって2013年に論じられましたが、私は更に詳しく追求する意義があると考えます。

Benの投稿は、HTMLクラスをグループに分割する際にスラッシュ(”/”)を使用することで、コードを読みやすくし、可読性を上げるための方策でした。彼が指摘しているように、エスケープ処理されていないスラッシュは無効文字ではありますが、ブラウザはそれを問題として扱うことなく、単に無視します。

次のHTMLの例を見てみましょう。

<div class="col col-4 col-8 c-list bx bx--rounded bx--transparent">

スラッシュを入れて以下のような記述にします。

<div class="col col-4 col-8 / c-list / bx bx--rounded bx--transparent">

無効文字であるかどうかに関わらず、どのような文字を挿入した場合でも同じ結果を得られます。

<div class="col col-4 col-8 ** c-list ** bx bx--rounded bx--transparent">

<div class="col col-4 col-8 || c-list || bx bx--rounded bx--transparent">

<div class="col col-4 col-8 && c-list && bx bx--rounded bx--transparent">

いずれのコード記述でも正常に作用するようです。以下の例で試してみてください。

もちろん、これらの区切り文字はスタイルシートでクラスとしては使用できません。そういう意味で私は”無効”としています。従って次のような記述はルールに準じておらず、特定のスタイルには適用できないでしょう。

./ {
  color: blue;
}

もし、CSS内でこれらの無効文字をHTMLクラスで使用し、ターゲットにしなければならない場合は、こちらのツールを使ってスラッシュを挿入し、エスケープすることができます。先ほどの例でいうと、CSSは次のような記述になります。

.¥/ {
  color: blue;
}

さらに言うと、Unicode文字はまったくエスケープ処理される必要がないので、以下のようなことさえもできるのです。

<div class="♥ ★"></div>

そして、CSSでは次のような記述になるでしょう。

.♥ {
  color: hotpink;
}

.★ {
  color: yellow;
}

あるいは、これらの文字も、直接挿入せずに、先ほどと同様にエスケープできます。以下の例は、上のコードブロックと同じことを意味します。

. \2665 {
  color: hotpink;
}

. \2605 {
  color: yellow;
} 

8. アニメーションの繰り返し再生が分数値で指定できる事実

CSSでキーフレームのアニメーションを記述する際、animation-iteration-countプロパティを使用してアニメーションの再生回数を指定することは、おそらくご存知でしょう。

.example {
  animation-iteration-count: 3;
}

この例では、アニメーションをフルタイムで3回実行することが整数値で指定されています。しかし、分数値を指定できることはご存知ないのではないでしょうか。

.example {
  animation-iteration-count: .5;
}

この場合、アニメーションは半数回実行されます(つまり、最初の再生の途中で停止します)。以下の例では、ページ上で2つのボールが動作します。上のボールは再生回数を”1″に指定していますが、下のボールの再生回数は”0.5″と指定しています。

追記:コメントで指摘をされましたが、これらのデモはデスクトップ版およびモバイル版のSafariでは正常に動作しません。これはfill modeに関係するバグのためで、既にこちらに報告し、修正されました。次の更新版で反映されるはずです。

この例で興味深いのは、再生間隔が描画のプロパティ/値に基づいているわけではないという点です。つまり、何かを100pxで描画する場合、中間点は必ずしも50pxではないのです。例えば、先ほどのアニメーションはlinearというタイミング関数を使用しているので、下のボールが横軸上の中間で止まったように見えます。

以下は、同じ2つのボールのアニメーションですが、easeというタイミング関数を使用した例です。

今度は下のボールが中間点を超えてから停止していますね。繰り返しになりますが、これは異なるタイミング関数を使用したことによって起きています。

タイミング関数を理解していれば、ease-in-outで値を指定した場合もlinearを使用したときと同じボールの位置になることにお気づきでしょう。いろいろな結果が得られますので分数値とタイミング関数で試して遊んでみてください。

9. アニメーション名がショートハンドを壊してしまう事実

これは数人の開発者が偶然見つけた事象で、仕様で注意喚起されています。例えば次のようなアニメーションコードがあるとします。

@keyframes reverse {
  from {
    left: 0;
  }

  to {
    left: 300px;
  }
}

.example {
  animation: reverse 2s 1s;
}

ここではreverseというアニメーション名を使ってみました。一見問題なさそうですが、実際にこのコードを動かしてみるとどのような結果になるのか確認してみましょう。

アニメーションは動きません。なぜなら、”reverse”という値が、animation-directionプロパティの値にも指定できるキーワードに相当しているからです。ショートハンドシンタックスで有効なキーワード値とアニメーション名が一致する場合には、常に同様の事象が起こります。ロングハンドの場合には問題ありません。

このようにショートハンドシンタックスを壊すアニメーション名としては、例えばinfinitealternaterunningpausedといったタイミング関数のキーワードも含まれます。

10. 要素が範囲選択できる事実

誰が最初にこの方法を使い始めたのかは分かりませんが、私が最初にこのやり方を見たのはGunnar Bittersmannによるこちらのデモです。20個の要素を持つ順リストがあり、このうち7番目から14番目までの要素を全て選択したい、としましょう。この場合次のように、セレクタ1つで実行することができます。

ol li:nth-child(n+7):nth-child(-n+14) {
  background: lightpink;
}

追記: コメント欄でのご指摘の通り、Safariにはこのテクニックの実行を妨げるバグがあります。幸いなことに、Matt Pomaskiの提案方法でこの問題を解決することができるようです。コードチェーンの順番を次のように単純に入れ変えてみてください。ol li:nth-child(-n+14):nth-child(n+7)となります。この方法で今のところWebKitではバグは出てきていませんので、Safari上でも問題なくこの方法を使うことができる、と言えるでしょう。

このコードには、構造疑似クラスが連鎖して使われています。少し分かりづらい式になっていますが、選択したい範囲が式の中の数値で示されているのが確認できます。

どのように実行しているか正確に見てみましょう。連鎖の最初にある式には、”7番目の要素を選び、その後に続く全ての要素も選択する”と書かれています。次のパートには、”14番目の要素を選び、その前にある全ての要素も選択する”と書かれています。2つのセレクタは連鎖しているので、各パートはそれぞれのスコープを限定します。すなわちこの場合、2番目に出てきたパートは、最初のパートが14番目より先にある要素を選択することを許容しません。また最初のパートは、2番目のパートが7番目より前にある要素を選択することを許容しません。

こういったタイプのセレクタや式に関する詳細は、以前こちらの記事にまとめましたので、ご確認ください。

11. 疑似要素を空要素に適用できる事実

もしかしたら皆さんも私のように、疑似要素をイメージやフォームのインプットに当ててみようと考える時があるかもしれません。しかし疑似要素とは置換要素に対しては機能しないものなので、この試みはうまく動きません。そこで開発者の皆さんの多くは、全ての空要素(終了タグを持たない要素)には疑似要素が適用できない、と想定されるかと思います。しかしそれは正解ではありません。

置換要素ではないいくつかの空要素には、疑似要素が適用できるものもあるのです。次のデモで見られるhr要素もこのケースの1つです。

この例における色付きエリアは、水平方向のルール(hr要素)に従っています。そしてこの要素には、::before::afterという疑似要素が適用できています。でも面白いことに、同じく置換要素ではない空要素であるbr要素を使った場合には、適用可能という結果は得られません。

メタタグとlink要素に対しても疑似要素を加えることができます。そのためには次のデモのように、これらの要素をdisplay: blockになんとかうまく変換することが必要です。

12. セレクタ内で大文字・小文字の区別が不要な属性値があるという事実

最後に取り上げるこの事実は、改めて特筆するべきものではないかもしれません。次に挙げるHTMLがあるとしましょう。

<div class="box"></div>
<input type="email">

属性セレクタを使えば、次のようにこれらの要素をスタイリングすることができます。

div[class="box"] {
  color: blue;
}

input[type="email"] {
  border: solid 1px red;
}

これはうまく動くようですが、では次のコードではどうでしょう?

div[class="BOX"] {
  color: blue;
}

input[type="EMAIL"] {
  border: solid 1px red;
}

2番目の例では、属性の値はどちらも大文字で書かれています。この場合、.box要素にはスタイルが適用されません。なぜならclassの属性は大文字と小文字を区別する、いわゆるケース・センシティブなものだからです。一方typeの属性値はケース・センシティブではないので、emailのフィールドには正しくスタイルが適用されます。この場で取り上げる必要はなかったかもしれませんが、でももしかしたら皆さんが気づいていなかったかもしれない事実です。

以上です、皆さん

これで幕は下りました。この続編が皆さんにとっては、そんなに安っぽいものではなかったことを祈ります。私は毎週のように、このようなちょっとしたユニークなCSS豆知識を学んできました。この記事の中から少しでも目新しいものを、多くの皆さんが見つけることを願っています。CSSのあいまいなトリックにテクニック、皆さんのお気に入りの情報はどれだったのか、気になっています。あまりご存知なかったけれども、実際ブラウザはサポートしていた素敵なプロパティや機能が見つかったかどうか、教えて下さい。コメントをお待ちしています。