これは何
2021年11月5日に開催されるCSS Nite Coder's High - 2021 fallにて発表する内容です。
当日はスライドを用いて発表しますが、コードの見やすさの問題もあるためQiitaに原稿を投稿します。
導入
それでは今から「そのCSS、新しい書き方がありますよ」と題してセッションをはじめていきます。
まずは簡単に自己紹介から。
Increments株式会社でデザイナーをしています、綿貫佳祐と申します。
企画考えたり情報設計したり目に見えるUI作ったりフロントエンドやったり……比較的色々やっている方です。
また、このxrxoxcxoxのIDで各種SNSやっておりますので良かったらフォローしてください。
1番動いているのはTwitterです。
さてそれでは内容に入っていきますが……CSS、色んな書き方がありすぎると思いません?
最終的なビューが同じなら、便利な書き方・分かりやすい書き方をしたいですよね?
既によく知られている書き方でも決して問題は無いものの、新しい書き方を覚えて楽しちゃおう!がこのセッションの趣旨です。
ただ、先にご了承いただきたいのが、一応ある程度は広く使えるテクニックを持ってきてはいるものの限界があります。
「新しいCSS」と言っている以上Internet Explorerでは使えないものが多いとか、そういう具合ですね。
実務ではCan I use...などをご覧の上ご使用くださいませ。
というわけでこれから8つのテクニック+オマケ2つ、全部で10のトピックをご紹介します。
- Flex Container内の余白
- 親要素に合わせた伸縮
- 絶対配置での中心揃え
- 画像のアスペクト比の維持
- 上下だけor左右だけの余白
- 推奨値、最小値、最大値
- 詳細度の制御
- フォーム要素のトンマナ
- SVGの線幅の維持
- margin: 0 auto
Flex Container内の余白
それではようやく具体的な話をしていきます。
まずはflexに対するテクニック。
flex container内の要素間に一定の余白を持たせたいシーン、しょっちゅうあるのではないでしょうか。
- 親要素の中に子要素が配置されている
- 子要素の数は不確定で、折り返しがある
- 子要素間に一定の余白を設けたい
flexに限りませんが昔からの手段としてコードを書くならきっとこんな感じ。
<div class="parent">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
.parent {
display: flex;
flex-wrap: wrap;
padding: 16px 8px 8px 16px;
}
.child {
height: 50px;
width: calc(20% - 8px);
margin-bottom: 8px;
margin-right: 8px;
}
これらのコードの嫌なところは……
- 親要素で余白を差し引きした調整をすると
- 親と子、両方のスタイルを見ないと意図が分からない
- それぞれの責務から微妙に逸脱している感がある
- 直感的でない、美しくない
ではここでAfterを見てみましょう。
HTMLは同じままで……
.parent {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 16px;
}
.child {
height: 50px;
width: calc(20% - 8px);
}
アップデートした内容はこのようになっています。
- gap プロパティを使用する
- 親要素から余白を指定できる
- つまり、子要素には何の余白も指定しなくて良くなる
- 垂直の余白と水平の gap を別個に指定も可能
親要素に合わせた伸縮
お次は、今の話に加えて「子要素の伸縮」を制御しようという話です。
組みたいのはこういう状態。
- 子要素が親要素をぴったり埋める
- 子要素の数は不確定で、折り返しがある
- 最後の行もちゃんとグリッドに揃う
こちらも意外と面倒で……まずは先ほどのAfterのコードをそのまま持ってきましょう。
<div class="parent">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
.parent {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 16px;
}
.child {
height: 50px;
width: calc(20% - 8px);
}
嫌なところはこんな具合です。
- flex-grow を使うと最後の行の要素がとても伸びてしまう
- 要素数が分からないから :nth-last-child も使えない
- 3列までなら疑似要素を使って解決できるけど4列以上は難しい
- 要素数が不確定なので空divなどを使っても面倒
こちらを書き直すと……
.parent {
display: grid;
gap: 8px;
grid-template-columns:
repeat(auto-fit, minmax(100px, 1fr));
padding: 16px;
}
.child {
height: 50px;
}
アップデートした内容はご覧の通り。
- display を flex から grid へ
- repeat() + auto-fit
- 余白がある分だけ繰り返してくれるテクニック
- minmax() + fr
- 最小値と最大値を柔軟に指定するテクニック
絶対配置での中心揃え
次は、position: absoluteでのちょっとした効率化です。
position: relative な親要素の内側の absolute な子要素、これを上下左右の中心に揃えます。
<div class="parent">
<div class="child"></div>
</div>
.parent {
position: relative;
}
.child {
height: 50%;
width: 50%;
position: relative;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
それこそ冒頭に話した中央揃えテクニックのうちの1つなわけですが、top, right, bottom, leftすべてに0を指定するんですよ。
こう……分かるんですけど、冗長ですよね。
こちら書き換えてみますとこうなります。
.parent {
position: relative;
}
.child {
height: 50%;
width: 50%;
position: absolute;
inset: 0;
margin: auto;
}
- 位置の指定に inset を使った
- top, right, bottom, left のショートハンド
- margin や padding と同様、上下と左右に分けて記述可能
画像のアスペクト比の維持
どんどん行きます。次は画像のアスペクト比の維持です。
- 親要素の変形に影響されず、画像のアスペクト比を維持したい
- 今回は16:9とする
<div class="parent">
<img class="child" src="path/to/image" alt="">
</div>
.parent {
position: relative;
padding-top: 56.25%;
overflow: hidden;
}
.child {
position: absolute;
inset: 0;
margin: auto;
}
これまでのコードが嫌なのは、正直全部意味不明な点です。
- position: relative と overflow: hidden の組み合わせ
- padding-top: 56.25%って何?
- → 9/16 = 0.5625
- しかも bottom でも良くて一貫性が無い
- img は absoluteかつ中央揃え
一事が万事謎ですよね。
56.25%って何?padding-topかbottomどっちの方が良い?とか、やたら面倒くさい。
それに対して新しい書き方は非常にシンプルです。
なんというか、コードを見たままじゃないですか?
<img class="image" src="path/to/image" alt="">
css .image { aspect-ratio: 16 / 9; object-fit: cover; }
- HTML で、img を囲っている div を削除
- aspect-ratio を使った
- 16 / 9 のように一目で見て分かる記述が可能
上下だけor左右だけの余白
次です。
上下にだけpaddingをつけたい場面があったとして、みなさんどっちで書きます?
padding: 10px 0;
padding-top: 10px;
padding-bottom: 10px;
質問しといて全然違うこと言うんですが、僕はどっちも好みません……。
- 初期値が 0 なのにわざわざ上書きしたくない、WET
- ショートハンドがあるのに個別で記述するのももったいない
- 上下だけでなく、左右だけの場合も同様
- padding だけでなく、 margin の場合も同様
ですが、こんな書き方ができます。
.top-and-bottom {
padding-block: 10px;
}
.left-and-right {
padding-inline: 10px;
}
論理的プロパティというものです。
普通のUIは横書きですので、この場合は左右に余白を持たせたければpadding-inline
だし上下ならpadding-block
を使えばOK。
わざわざ0を書かずとも狙った箇所だけ値を指定できます。
なお、論理的プロパティについてはそれだけで1つセッションができるようなトピックだと思うので、ここでは詳しくは触れません。
もし気になる方がいればリファレンスとしてリンクを載せてあるのでそちらからご覧ください。
推奨値、最小値、最大値
次は、イベントページに例として載せていた話です。
- 基本は親要素の50%幅
- だけど最大400px
- かつ最小100px
素直に書くなら
.element {
width: 50%;
max-width: 400px;
min-width: 100px;
margin: auto;
}
こうなりまよね。
全然問題はないのですが、僕は本当にものぐさなので3行書くのは多いなあって気持ちになってしまうんですよね笑
書き換えてみましょう。
.element {
width: clamp(100px, 50%, 400px);
margin: auto;
}
clamp()
という関数を使うとこちらを1行で表現できます。
- clamp() 関数の使用
- 最小値、推奨値、最大値の3つの引数をとります
- 振る舞いは min-width や max-width の指定と同じ
- 似たような関数
- min(), max()
- 2つの引数をとり、小さい方 or 大きい方になる
詳細度の制御
終盤にさしかかってきました。次は:where()
です。
MarkdownからHTMLとして出力したページのスタイルを指定するとか、CMSから入稿された記事のスタイルを書く際とか、そういうときに使えるかな?と思います。
どういうことかと言うと、今言ったシチュエーションってタグに直接スタイルを指定しがちですよね?
pタグとpタグが続いたらこのmargiin、とか、そういう感じです。
タグに直接指定だけならともかく、隣接セレクタとか子孫セレクタとかを使わざるを得ない場合だと詳細度が上がってしまうじゃないですか。
すると、改めてclassを定義してスタイルを当てようと思ったら詳細度で負けて上手く当たらない……そんな経験ないですか?
/* ベースのスタイル */
p:not(:first-of-type) {
margin-top: 10px;
}
/* 上書きしたいスタイル */
.element {
margin-top: 20px !important;
}
これを回避してみせましょう
/* ベースのスタイル */
:where(p:not(:first-of-type)) {
margin-top: 10px;
}
/* 上書きしたいスタイル */
.element {
margin-top: 20px;
}
- :where() の使用
- この中で指定すると、詳細度が上がらなくなる
- そのため、 After は !important 無しでもスタイルがあたる
念のため、Beforeのコードから!important
外してみると詳細度で負けちゃいます。
!important
じゃない方法で詳細度を上げる方法もいくらでもありますけど、わざわざ詳細度を上げるコードって、不自然なコードですよね。
それに今は良くても未来の負債になること間違いなし……。
ここは1つ、平和のためにも:where()
を使っておきましょう。
フォーム要素のトンマナ
オマケ以外のテクニックではこれが最後です。
ラジオボタンとかチェックボックスとか「CSSでこんな飾り付けができる!」って記事もたくさんありますけど……。
- やりがちなこと
- checked や indeterminate まで CSS で表現する
- 元々の要素を display: none で隠してから装飾する
- 実装時は良くても
- 後から解読するのが大変
- HTML に依存してしまい融通がきかない
- アクセシビリティが下がる
<input
type="checkbox"
class="check"
id="check"
>
<label
class="label"
for="check"
>
ラベル
</label>
.check {
/* スタイル色々 */
}
.label::before {
/* スタイル色々 */
}
.label::after {
/* スタイル色々 */
}
.check:checked + .label::after {
/* スタイル色々 */
}
こういうスタイルを書くとですね
- トンマナを合わせたいだけにしては重労働
- HTML要素としてのデフォルトを打ち消している
- 何が起きるか、何ができなくなるか、予想できない
とは言えサイトのトーンとフォーム要素のトーンを合わせたい、間違いありません。
というわけで……
.check {
accent-color: #55c500;
}
accent-color
を使いましょう。
- accent-colorの使用
- default, checked, など状態に合わせて色をつけてくれる
- 最悪、フォールバックを用意せずとも崩れることは無い
- user agent stylesheet が当たるだけ
今回はコードとして例を示しやすくするために個別の要素にスタイルをあてていますが、resetというかfoundationというか、そういうレイヤーで指定すればOKです。
おまけ SVGの線幅の維持
そしておまけの1つ目です。
これもはやCSSの話じゃないんですけど……笑
SVGって拡大しても荒れないから便利なんですけど、拡大したときに線幅は変わらないで欲しい場面、ないですか?
画像としてのSVGというよりはインタラクティブな描画の要素としてSVGを使うとあるかなあと思っています。
そんなときには
vector-effect="non-scaling-stroke"
が使えます。
もう全部説明したようなものですが、これを指定しておくと要素全体が拡大されても線幅だけは拡大されません。
おまけ margin: 0 auto
そしておまけ2つ目。
こちらは、別に全然新しくもなんともないんですが、効率化できるという意味で軽くご紹介します。
また中央揃えの話ですが、margin: 0 auto
お世話になってますよね。
そしてこれ、左右中央揃えしたいだけならmargin: auto
で良いんですよ。
2文字だけですけど、減らせます。
仕組みは単純で、ショートハンドでautoってことは上下もautoになります。
なので左右中央揃えをしたいという意図なら0
は要らないんです。
まとめ
というわけで10の話題を紹介しました。
- Flex Container内の余白
- 親要素に合わせた伸縮
- 絶対配置での中心揃え
- 画像のアスペクト比の維持
- 上下だけor左右だけの余白
- 推奨値、最小値、最大値
- 詳細度の制御
- フォーム要素のトンマナ
- SVGの線幅の維持
- margin: 0 auto
かなり駆け抜けて説明しましたが、どうでしょう、少しでもみなさんのお役に立てそうであれば幸いです。
新しすぎてどのブラウザでも使えない:has()
の話とか、もうちょっとでChromeでも使えるはずのsubgrid
の話とか、そういうのは今回は抜きました。
ただ、CSSは日進月歩なのでこれからも引き続き色々ウォッチしていこうと思います。
以上で私のセッションは終了です。ご清聴ありがとうございました。
最後まで読んでくださってありがとうございます!
Twitterでも情報を発信しているので、良かったらフォローお願いします!