CSSは確実に進化している! 変数、条件分岐、ループ、論理演算など、ロジックに記述するCSSの実装テクニック
Post on:2022年3月17日
一昔前のCSSと比較すると、ここ数年でCSSはかなり進化しました。calc()
で数式が扱えるようになり、変数、条件分岐、ループ、論理演算なども使用できます。CSSでロジックをどう記述するのか、ブログラミング言語的な実装を紹介します。
Writing Logic in CSS
by Daniel Schulz
TwitterでCSSがプログラミング言語なのかと話題になっていました。その前から本記事の翻訳に取り組んでいたのですが、非常に興味深い記事でした。CSSは宣言型プログラミング言語ですが、JavaScriptのような命令型の要素も増えてきて、一昔前からかなり進化しています。
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
はじめに
CSSは、スタイルのシステムに特化された専門性の高いプログラミング言語と言えるかもしれません。その独自な使用方法と宣言型の性質から、時に理解するのが難しい場合があります。
一部の人は、CSSがプログラミング言語であることを否定するかもしれません。スマートで、フレキシブルなスタイルのシステムをプログラミングすることで、彼らの間違いを証明しましょう。
CSSの制御構造
より伝統的な汎用言語(JavaScriptなど)では、条件(if
/then
)、ループ(for
, while
)、論理式(==
, &&
)、変数などのツールがあります。これらの構造はCSSでは異なる名前が付けられており、その構文はドキュメントのスタイル付けという特定のユースケースにうまく対応するために大きく異なります。また、それらの一部は数年前まではCSSで使用できませんでした。
CSSの変数
CSSの変数は、とても分かりやすいです。CSSではカスタムプロパティと呼ばれています(ただし、多くのデベロッパーは変数と呼び、独自の構文もそう呼びます)。
1 2 3 4 5 6 |
:root { --color: red; } span { color: var(--color, blue); } |
変数は二重のダッシュで宣言をし、値を代入します。セレクタの外で使用するとCSS構文が破損するので、スコープ内で定義する必要があります。:root
セレクタに変数を定義すると、グローバルスコープとして機能します。
参考: CSSの変数(カスタムプロパティ)便利な使い方を詳しく解説
CSSの条件
CSSの条件は、使用する場所に応じてさまざまな書き方があります。セレクタは要素にスコープされ、メディアクエリはグローバルにスコープされ、独自のセレクタを必要とします。
属性セレクタ
1 2 3 4 5 6 7 8 9 |
[data-attr='true'] { /* if */ } [data-attr='false'] { /* elseif */ } :not([data-attr]) { /* else */ } |
疑似クラス
1 2 3 4 5 6 |
:checked { /* if */ } :not(:checked) { /* else */ } |
メディアクエリ
1 2 3 4 5 6 7 8 |
:root { color: red; /* else */ } @media (min-width > 600px) { :root { color: blue; /* if */ } } |
CSSのループ
カウンターは、CSSのループで最もわかりやすい形式であると同時に、最も使用例が少ないものです。カウンターはcontent
プロパティにのみ使用でき、テキストとして表示されます。カウンターの増分、開始点、任意の時点での値を調整できますが、出力は常にテキストに限られます。
1 2 3 4 5 6 7 8 9 10 11 12 |
main { counter-reset: section; } section { counter-increment: section; counter-reset: section; } section > h2::before { content: 'Headline ' counter(section) ': '; } |
ループを使用して繰り返されるレイアウトパターンを定義したい場合はどうしたらよいでしょうか。この種のループは、少しわかりにくいかもしれません。答えは、CSS Gridのauto-fill
プロパティです。
1 2 3 4 |
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } |
auto-fill
プロパティは利用可能なスペースを埋めるようにアイテムを拡大縮小しながら、必要な場合は複数の行に分割して、グリッドに収まるだけの要素で満たします。これはアイテムが見つかる限り繰り返され、上記のCSSでは最小幅300px、最大幅は自身のサイズの1/4に制限されます。説明するよりも下記のデモを見た方が分かりやすいでしょう。
See the Pen
CSS Fluid Grid by Daniel Schulz (@iamschulz)
on CodePen.
そして最後に、ループ状のセレクタがあります。これは引数を取るもので、正確に要素を選択するための数式を使用できます。
1 2 3 4 5 6 7 |
section:nth-child(2n) { /* すべての偶数要素を選択します */ } section:nth-child(4n + 2) { /* 2番目から4番目ごとに選択します */ } |
特別なユースケースとして、:nth-child()
と:not()
を組み合わせることもできます。
1 2 3 |
section:nth-child(3n):not(:nth-child(6)) { /* 3番目の要素ごとに選択します、ただし6番目は選択しない */ } |
下記のデモは、3番目の要素ごとにイエローにし、6番目だけはずされています。
See the Pen
Loop Selectors by Daniel Schulz (@iamschulz)
on CodePen.
:nth-child()
を:nth-of-type()
や:nth-last-of-type()
に置き換えて、スコープを変更することもできます。
CSSの論理演算
Ana TudorはCSS Logic GatesでCSSの論理演算について書いています。その記事では変数をcalc()
と組み合わせるというアイデアに取り組んでおり、それを使用した3Dモデリングやオブジェクトのアニメーションを見ることができます。
1 |
--and: calc(var(--k)*var(--i)) |
難解な呪文のようですが、記事を読み進めると、より狂気じみたものになり、CSSがプログラミング言語である理由の最も良い説明の1つです。
CSSの実装テクニック
フクロウセレクタ
フクロウセレクタは、あるアイテムに続くすべてのアイテムを選択するセレクタです。例えば、margin-top
を定義すると、grid-gap
のようにアイテム間にギャップが追加されますが、CSS Gridは必要ありません。
1 2 3 |
* + * { margin-top: 1rem; } |
また、カスタマイズがより可能となります。margin-top
を上書きして、あらゆる種類のコンテンツに適応させることができます。各アイテム間に1rem
のスペースが必要だが、見出しの上には3rem
のスペースを確保したい、そんな時はCSS Gridよりもフクロウセレクタの方が簡単にできます。
Kevin Pennekampではフクロウセレクタのアルゴリズムを説明しています。
参考: フクロウみたいなセレクタ
条件付きスタイル
CSSのコードにトグルを作成し、変数とcalc()
を使用して特定のルールをオン・オフすることができます。これにより、非常に用途の広い条件を実現できます。
1 2 3 4 5 6 7 8 9 10 11 |
.box { padding: 1rem 1rem 1rem calc(1rem + var(--s) * 4rem); color: hsl(0, calc(var(--s, 0) * 100%), 80%); background-color: hsl(0, calc(var(--s, 0) * 100%), 15%); border: calc(var(--s, 0) * 1px) solid hsl(0, calc(var(--s, 0) * 100%), 80%); } .icon { opacity: calc(var(--s) * 100%); transform: scale(calc(var(--s) * 100%)); } |
--s
と.box
の値に応じて、アラートのスタイルを有効にしたり無効にします。
See the Pen
Conditional Styles by Daniel Schulz (@iamschulz)
on CodePen.
自動コントラストカラー
同じこのロジックをさらに一歩進めて、背景色とのコントラストに依存するカラー変数を作成してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
:root { --theme-hue: 210deg; --theme-sat: 30%; --theme-lit: 20%; --theme-font-threshold: 51%; --background-color: hsl(var(--theme-hue), var(--theme-sat), var(--theme-lit)); --font-color: hsl( var(--theme-hue), var(--theme-sat), clamp(10%, calc(100% - (var(--theme-lit) - var(theme-font-threshold)) * 1000), 95%) ); } |
このCSSは、HSL値と黒または白のフォントカラーから、背景の明度値を反転して背景色を計算します。これだけでは色のコントラストが低くなってしまうので(60%のグレーの背景に40%のグレーのフォントは読みにくい)、閾値(色が白から黒に切り替わるポイント)を引き、1000のような非常に高い値をかけて10%と95%の間で固定し、最終的に有効な明度のパーセントを得るようにします。CSSの先頭にある4つの変数を編集することで、すべてを制御できます。
See the Pen
Pure CSS Auto-Contrast Color by Daniel Schulz (@iamschulz)
on CodePen.
このテクニックは、HSL値のみに基づいて複雑なカラーロジックや自動テーマを作成するのにも使用できます。
スタイルシートのクリーンアップ
これまでのものを組み合わせて、スタイルシートをすっきりとクリーンアップさせましょう。ビューポートですべてをソートするのは少しスパゲッティな感じがしますが、コンポーネントでソートするのはもっといい感じがしません。変数を使用すると、両方の長所を生かすことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* define variales */ :root { --paragraph-width: 90ch; --sidebar-width: 30ch; --layout-s: "header header" "sidebar sidebar" "main main" "footer footer"; --layout-l: "header header" "main sidebar" "footer footer"; --template-s: auto auto minmax(100%, 1fr) auto / minmax(70%, var(--paragraph-width)) minmax(30%, var(--sidebar-width)); --template-l: auto minmax(100%, 1fr) auto / minmax(70%, var(--paragraph-width)) minmax(30%, var(--sidebar-width)); --layout: var(--layout-s); --template: var(--template-s); --gap-width: 1rem; } /* manipulate variables by viewport */ @media (min-width: 48rem) { :root { --layout: var(--layout-l); --template: var(--template-l); } } /* bind to DOM */ body { display: grid; grid-template: var(--template); grid-template-areas: var(--layout); grid-gap: var(--gap-width); justify-content: center; min-height: 100vh; max-width: calc( var(--paragraph-width) + var(--sidebar-width) + var(--gap-width) ); padding: 0 var(--gap-width); } |
See the Pen
Styling with Variables by Daniel Schulz (@iamschulz)
on CodePen.
すべてのグローバル変数は最上部で定義され、ビューポートごとにソートされています。事実上「ビヘイビアの定義」となり、以下の問題をクリアにします。
- このスタイルシートにどんなグローバルな側面がありますか?
font-size
、カラー、反復記号などを考慮しています。 - 頻繁に変更される側面はありますか?
コンテナの幅、グリッドレイアウトなどが考えられます。 - ビューポート間で値をどのように変更する必要がありますか? どのグローバルスタイルがどのビューポートに適用されますか?
コンポーネントごとにソートされたルール定義です。メディアクエリは上部で定義されており、変数に格納されているので、ここではもう必要ありません。あとは、スタイルシートに沿ってコードを書くだけです。
ハッシュパラメータの読み取り
擬似クラスの特殊なケースとして、URLのハッシュフラグメントを読み取ることができる:target
セレクタがあります。この仕組みを利用して、SPAのような体験をシミュレートするデモを紹介します。
See the Pen
CSS only Single Page App by Daniel Schulz (@iamschulz)
on CodePen.
私はこれに関する記事を書きました。ただ、これはアクセシビリティに大きく関わることで、実際にバリアフリーにするためにはJavaScriptの仕組みが必要であることに注意してください。 ライブ環境ではこれを行わないでください。
JavaScriptで変数を設定する
CSS変数の操作は、今では非常に強力なツールになっています。JavaScriptでも活用することができます。
1 2 3 4 5 6 7 8 9 |
// set --s on :root document.documentElement.style.setProperty('--s', e.target.value); // set --s scoped to #myID const el = document.querySelector('#myID'); el.style.setProperty('--s', e.target.value); // read variables from an alement const switch = getComputedStyle(el).getPropertyValue('--s'); |
上記の条件付きスタイルのデモは、まさにそのように動作します。
終わりに
CSSはスマートで、リアクティブなレイアウトシステムを実現する能力が非常に高い言語です。CSSの制御構造とアルゴリズムは他の言語と比べると少し奇妙かもしれませんが、CSSはそこに存在し、タスクに対応することができます。スタイルについて説明を見るだけでなく、実際に動かせるようにしましょう。
sponsors