CSSにおけるロジックの書き方

CSSは、スタイルシステムにフォーカスした特化したプログラミング言語です。このユニークな使用例と宣言的な性質のため、時に理解するのが難しいことがあります。中にはプログラミング言語ではないと否定する人たちもいます。それを間違っていると証明しましょう。スマートで柔軟なスタイルシステムをプログラムすることによってです。

より伝統的な、汎用的な言語(JavaScriptなど)では、条件分岐(if/then)、ループ(for, while)、論理ゲート(===, &&など)、変数といったツールを使います。これらの構造にはCSSでは違う名前がついており、文書をスタイルするという特定の使用例に適応するために、その構文は大きく異なっています。またそれらのいくつかは、これまでの数年間CSSには存在しませんでした。

変数

変数は最も直截的なものです。CSSではカスタムプロパティと呼ばれますが(とはいえ、皆普通に変数と呼んでいます。それ自体の構文でさえも)、それらは次のように宣言されます。

:root {
    --color: red;
}
span {
    color: var(--color, blue);
}

ダブルダッシュが変数を宣言し、値を割り当てます。これは必ずスコープ内で行わなければなりません。なぜなら、セレクタの外で行うとCSSの構文が破綻するからです。:rootセレクタが、グローバルスコープとして機能します。

条件分岐

条件分岐は用いる場所に応じて、いくつかの方法で記述することができます。セレクタはそれぞれの要素に対してスコープされ、メディアクエリはグローバルにスコープされ、それ自身のセレクタが必要です。

属性セレクタ:

[data-attr='true'] {
    /* if */
}
[data-attr='false'] {
    /* elseif */
}
:not([data-attr]) {
    /* else */
}

疑似クラス:

:checked {
    /* if */
}
:not(:checked) {
    /* else */
}

メディアクエリ:

:root {
    color: red; /* else */
}
@media (min-width > 600px) {
    :root {
        color: blue; /* if */
    }
}

ループ

カウンターはCSSでのループの中で最も直截的な形ですが、利用の場合は非常に限定されています。カウンターを使うことができるのはcontentプロパティ内だけで、テキストとして表示されます。これを増分、開始点、任意の点での値と調整することができますが、出力は常にテキストに限定されます。

main {
    counter-reset: section;
}

section {
    counter-increment: section;
}

section > h2::before {
    content: 'Headline ' counter(section) ': ';
}

ではループを使って反復するレイアウトパターンを定義したいとしたらどうでしょうか?この種のループは少し分かりにくいものです。グリッドのauto-fillプロパティがそれに該当します。

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

これによってグリッドは、使用可能な空間を埋めながら要素をスケールすることができ、必要に応じて複数の行に分割します。アイテムが見つかる限り繰り返され、最小幅を300px、最大幅を自体のサイズの1分の1に制限します。これは説明するよりも実際に見る方が簡単です。

最後にループされたセレクタがあります。これらは論理式を引数として取り、非常に精密にセレクトすることができます。

section:nth-child(2n) {
    /* 偶数番目の要素を選択 */
}

section:nth-child(4n + 2) {
    /* 2番目から始まって、4番目ごとに選択 */
}

本当に特別なエッジケースでは、:nth-child():not() を組み合わせることもできます。

section:nth-child(3n):not(:nth-child(6)) {
    /* 3番目ごとの要素を選択、ただし6番目は除外 */
}

:nth-child():nth-of-type():nth-last-of-type() に置き換えることでこれら最後の数例のスコープを変更することができます。

Ana TudorさんはCSS論理ゲートについての記事を書いています。これらは変数を calc で組み合わせるアイディアに基づいています。彼女はそれを使って3Dモデル作成やオブジェクトのアニメーションまで進めています。それはほとんど魔法のようで、記事を読み進めるにつれてますます狂気じみてきて、CSSが確かにプログラミング言語であるという最高の説明の一つです。

フクロウセレクタ

* + * {
    margin-top: 1rem;
}

フクロウセレクタは、あるアイテムに続く全てのアイテムを選択します。margin-topを適用することで、グリッドシステムを使わずにアイテム間のギャップを効果的に追加します。つまり、これはもっとカスタマイズ可能です。アイテムごとに1remのスペースを作りたいけど、見出しの前には3rem欲しいと思ったら?それはフクロウセレクタを使ってグリッド内でやるよりも簡単にできます。

Kevin Pennekampさんはその上で、アルゴリズムを擬似コードで説明してくれる詳細な記事を持っています。

条件付きスタイリング

私たちは変数とcalcを使って、特定のルールをオンとオフに切り替えるトグルをCSSコード内に作ることができます。これによって非常に多用途な条件を作ることができます。

.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はそれ自体のアラートスタイルを有効または無効化します。

自動コントラストカラー

同じロジックをもう一歩進めて、背景色に対するコントラストに依存するカラー変数を作りましょう。

: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%)
    );
}

このスニペットは、HSL値から背景色を計算し、背景の明度値を反転させることにより黒か白のフォントカラーを作ります。これだけで色のコントラストが低くなることがあります(60%グレーの背景に40%グレーのフォントはほとんど読むことができません)。ですので、私は閾値(色が白から黒に変わる点)を引いて、非常に高い値である1000をかけ、最終的に有効な明度パーセンテージが得られるように10%から95%の間でクランプします。スニペットの最初にある4つの変数を編集することで全てコントロールできます。

この方法は、HSL値だけに基づいて複雑なカラーロジックや自動テーマを書くのにも使われます。

スタイルシートのクリーニング

ここまでのことを組み合わせてスタイルシートを綺麗にしましょう。ビューポートによって全てを整列させるのはちょっとめちゃくちゃに見えますが、コンポーネントごとに整列させるのもあまり良く感じません。変数を使えば両方の良い面を持つことができます。

/* 変数を定義 */
: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;
}

/* ビューポートごとに変数を操作 */
@media (min-width: 48rem) {
    :root {
        --layout: var(--layout-l);
        --template: var(--template-l);
    }
}

/* 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);
}

全てのグローバル変数はとても上の部分に定義され、ビューポートで整列されています。その部分は効果的に_行動の定義_になり、こんな疑問をクリアにしてくれます。

  • スタイルシートにおけるグローバルな側面には何がありますか?font-size、色、繰り返しの寸法などを考慮しています。
  • よく変わる側面には何がありますか?コンテナの幅、グリッドレイアウトなどが考えられます。
  • ビューポート間で値はどのように変わるべきですか?どのグローバルスタイルがどのビューポートに適用されますか?

以下はコンポーネントごとに整列されたルールの定義です。ここではもうメディアクエリは必要ありません。なぜならそれらはすでに上部で定義され変数に入れられているからです。この時点で私たちはスタイルシートに途切れなくコードを書き続けることができます。

ハッシュパラメータの読み取り

疑似クラスの特殊なケースとしては、:targetセレクタがあり、それはURLのハッシュフラグメントを読み取ることができます。SPAのような体験をシミュレートするデモを次に示します:

私はその投稿を書いています。ただしこれがいくつかのアクセシビリティ問題を持つことに注意してください。そして実際にバリアフリーにするには、いくつかのJavaScriptのメカニズムが必要です。ライブ環境でこれを行わないでください。

JavaScriptで変数を設定する

CSS変数を操作することがとても強力なツールになりました。我々はJavaScriptでもそれを利用することができます。

    // :rootに--sを設定
    document.documentElement.style.setProperty('--s', e.target.value);

    // #myIDにスコープされた--sを設定
    const el = document.querySelector('#myID');
    el.style.setProperty('--s', e.target.value);

    // 要素から変数を読み取る
    const switch = getComputedStyle(el).getPropertyValue('--s');

上記のcodepen はちょうどこのように動作します。

CSSはとてもインテリジェントで反応的なレイアウトシステムを定義することが可能です。その制御構造とアルゴリズムは他の言語と比較して少し奇妙かもしれませんが、それらは存在し、その任務に完全に準拠しています。単にスタイルを述べるのではなく、それらを_動作_させましょう。

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/iamschulz/writing-logic-in-css-3ig0