Open10

レスポンシブをスマートに記述するためのCSS変数とユーティリティクラスの関係を考えてみる

了
  • ユーティリティクラスの作り方の試行錯誤のメモ。
  • --prop,--prop--sm,--prop--md,--prop--lgみたいな変数で、各プロパティの管理を楽にしたい
  • ネストされた時に親要素の影響を受けないように注意する

現在、CSS設計理論というかフレームワークというか、なんと呼ぶべきかよくわからないものを作っているのですが、その設計に関して数ヶ月悩み続けてるので自分用にメモしています...

了

ブレークポイントの数が決まっていればこれが無難かな...?

.-fz{
    --fz: 1rem; // 初期値
    --fz--sm: var(--fz);
    --fz--md: var(--fz--sm);
    --fz--lg: var(--fz--md);

    font-size: var(--fz);

    @sm{
        font-size: var(--fz--sm);
    }
    @md{
        font-size: var(--fz--md);
    }
    @lg{
        font-size: var(--fz--lg);
    }
}

使用例

<p class="foo -fz">text...</p>
.foo{
    --fz: 1rem;
    --fz--sm: 1.25rem;
    --fz--md: 1.5rem;
}
了

propName: var(--prop); のみで完結できないか?(変数の上書きだけで対応できないか?)

.-fz{
    --fz--base: 1rem; // ベース値に初期値セット
    --fz--sm: var(--fz--base);
    --fz--md: var(--fz--sm);
    --fz--lg: var(--fz--md);

    // 実際に利用する変数
    --fz: var(--fz--base);

    @sm{
        --fz: var(--fz--sm);
    }
    @md{
        --fz: var(--fz--md);
    }
    @lg{
        --fz: var(--fz--lg);
    }

    // プロパティセットは一回でいける
    font-size: var(--fz);
}

※ 変数の上書きとプロパティの上書き、どちらの負荷が高いかは知らない。


使用例

<p class="foo -fz">text...</p>
.foo{
    --fz--base: 1rem;
    --fz--sm: 1.25rem;
    --fz--md: 1.5rem;
}
了

特定のプロパティ用のユーティリティではなく、特定の変数を扱えるユーティリティとしての活用も面白そう。

.-foo{
    --foo--base: unset; // ベース値に初期値セットのセットは必要
    --foo--sm: var(--foo--base);
    --foo--md: var(--foo--sm);

    // 実際に利用する変数
    --foo: var(--foo--base);

    @sm{
        --foo: var(--foo--sm);
    }
    @md{
        --foo: var(--foo--md);
    }
    @lg{
        --foo: var(--foo--lg);
    }
}

1列→2列→4列のグリッドカラムを作るのに活用してみる

<div class="grid-column -foo">
    <div>...</div>
    <div>...</div>
    <div>...</div>
    <div>...</div>
</div>
.grid-column{
    --foo--base: 1;
    --foo--sm: 2;
    --foo--md: 4;

    grid-template-columns: repeat(var(--foo), minmax(0,1fr));
}
了

NG集

.-foo{
    --foo: unset;
    --foo--sm: var(--foo);
    --foo--md: var(--foo--sm);

    @sm{
        --foo: var(--foo--sm);
    }
    @md{
        --foo: var(--foo--md);
    }
    @lg{
        --foo: var(--foo--lg);
    }
}

--foo を受け取る --foo--sm を --foo にセットする、という構造がアウト。破綻するケースが出てくる。

了

NG集2
1行少なくしようと思ったけど、ネスト状態の時に親の状態を初期値として引き継いでしまう可能性がある。(必ず--base を定義するなら問題ないが...)

.-fz{
    --fz: var(--fz--base, unset);
    --fz--sm: var(--fz--base);
    --fz--md: var(--fz--sm);

    @sm{
        --fz: var(--fz--sm);
    }
    @md{
        --fz: var(--fz--md);
    }
    @lg{
        --fz: var(--fz--lg);
    }

    font-size: var(--fz, 1rem);
}
了

方向成分などを持つプロパティに関して

いい感じにしようとすると頭パンクする。
がんばってみたが、いずれにせよ何かしら使用に関しての追加ルールを設けないと厳しそう。

ブレイクポイントごとにクラスを用意した方がシンプルだという結論に達した。

.-p { padding: var(--p) }
// -pl, -pr, ....

@include sm{
    .-p\@sm { padding: var(--p--sm) }
    // -pl\@sm, ...
}

@include md{
    .-p\@md { padding: var(--p--md) }
    // -pl\@md, ...
}

@include lg{
    .-p\@lg { padding: var(--p--lg) }
    // -pl\@lg, ...
}


使用例

<div class="foo -p -pY@sm">
...
</div>
.foo{
    --p:1em;
    --pY--sm:1.5em;
}
了

特定のプロパティ群と紐づいているものの扱いをどうするか。

gridやflexなど。

A案
.-grid{
    --gta: none;
    --gta--sm: var(--gta);
    --gta--md: var(--gta--sm);

    --gtr: none;
    --gtr--sm: var(--gtr);
    --gtr--md: var(--gtr--sm);

    --gtc: none;
    --gtc--sm: var(--gtc);
    --gtc--md: var(--gtc--sm);

    display: grid;
    grid-template-areas: var(--gta);
    grid-template-rows: var(--gtr);
    grid-template-columns: var(--gtc);

    @include sm{
        grid-template-areas: var(--gta--sm);
        grid-template-rows: var(--gtr--sm);
        grid-template-columns: var(--gtc--sm);
    }
    @include md{
        grid-template-areas: var(--gta--md);
        grid-template-rows: var(--gtr--md);
        grid-template-columns: var(--gtc--md);
    }
}

メリット: .-gridクラスさえつけていれば、gridに関するプロパティをCSS変数だけでブレイクポイントごとに指定できるようになる。

デメリット: 全部のグリッドで各プロパティを扱うわけではないので、無駄が多くなる


B案
.-grid{
    display: grid;
}
.-gta{
    --gta: none;
    --gta--sm: var(--gta);
    --gta--md: var(--gta--sm);

    grid-template-areas: var(--gta);

    @include sm{
        grid-template-areas: var(--gta--sm);
    }
    @include md{
        grid-template-areas: var(--gta--md);
    }
});

// gtc, gtrも同様に...

素直に-fzの例のように分ける例。

メリット: 無駄が少なくなる
デメリット: 使用するクラスが増える

了

全部ブレークポイントごとにクラス分けるべきか?

paddingの例のように、font-sizeのようなシンプルなプロパティも分けるべきかどうか。

.-fz { font-size: var(--fz) }

@include sm{
    .-fz\@sm { font-size: var(--fz--sm) }
}

@include md{
    .-fz\@md { font-size: var(--fz--md) }
}

@include lg{
    .-fz\@lg { font-size: var(--fz--lg) }
}

メリット・デメリット

ブレイクポイントの数をカスタマイズできるようにするなら、動的生成とかしやすそう。
Purge CSSとか使ってTree shaking?するなら、こっちのほうがCSS量は圧倒的に減るはず。
ただし、クラスが増える→HTML側のサイズが増える, 汚く見える.

ブレイクポイント指定がどこにされているかの可視化にはなるか。

了

各ユーティリティクラスでプロパティ上書きするか、CSS変数上書きするか?

.-fz { font-size: var(--fz) }

@include sm{
    .-fz\@sm { --fz: var(--fz--sm) }
}

@include md{
    .-fz\@md { --fz: var(--fz--md) }
}

@include lg{
    .-fz\@lg { --fz: var(--fz--lg) }
}

みたいにすべきかどうか。

→できればこうしたい。...が、コンポーネント化して扱う時にインラインスタイルで変数出力することを考えると、それぞれに !importantが必要になってしまう。(とはいえ !important は変数上書きに対してなので、上書きが必要になった場合はプロパティの方で上書きするのは簡単)

また、@mdに対してのみなにかプロパティを指定する場合などに対応できない。(ベース指定がいるので、-prop@mdだけを使いたいときも-propがいる)
[class*=-prop]などでベース値をセットすればこれは回避できるが、ベース値で変に干渉させたくないpaddingやmarginのプロパティなどにはこれも向かない。

必ずそこでベース指定もするだろう、というプロパティでは変数上書きでもいいかもしれない。(grid系など)