Every layout
Chapter 1
Box
全てはBox。Boxの組み合わせ
blockはフロー方向に、inlineは書字方向に。
整形コンテキスト
flex,grid
コンテンツ
コンテンツによって要素の形状やサイズが決まる
Webコンテンツは動的なものである前提。よってWebレイアウトは静的ではなく動的であると考えるべき。
box-sizing
レイアウトの計算や予想効率のためbox-sizingはborder-boxであるべき。
ただしコンテンツ自身のサイズが重要な場合(Centerレイアウト等)ではその限りではない。
code:css
* {
box-sizing: border-box;
}
要素のサイズは可能な限り内側のコンテンツと外側のコンテキストから導出されるべき。サイズを規定しない
意図の提示
ブラウザ自身の計算によって独自の結論を引き出させる。レイアウトがどのような形になるかをmin-heightやflex-basisに任せてしまうなど。
全ての状況・ユーザー・コンテンツであっても壊れた状況で送り届けられるのを防ぐ。
コンポジション
コンポジション
細かい部品を組み合わせる
ダイアログを作る -> .dialogのようなダイアログのcssを組む -> 間違い。ダイアログ以外でも同じようなスタイルがあるのに再利用できない。
レイアウトプリミティブ
それ自身で意味や意図を持たないもの。例えばJSでいうとbooleanはプリミティブ、オブジェクトはプリミティブではない(それ自身で意図を持つ)。
Every Layoutではレイアウトプリミティブを駆使する
ダイアログはUIパーツとしての意図がある、一方その構成要素には意図はない
プリミティブの組み合わせでダイアログをレイアウト。他のUIも同様にプリテミティブで構成できる。
https://scrapbox.io/files/61a035aefd57be001dcba4ba.png
幅を基準としたメディアクエリのブレイクポイントを使わないでコンテンツの動的な変更にレスポンシブに調整させる
単位
px指定はブラウザの動的な調整機能を無視する(絶対指定)。em,rem,ch,exにはそうした懸念がない(相対指定)。
font-sizeだけでなくmarging,padding,borderなどにもem,rem,ch,exを使える。
px指定やメディアクエリを使っていると、全体のフォントサイズの変更を行いたい場合に個別で全て変更が必要になる。相対指定ならルート要素を変更するだけで動的に変更される。
ビューポート
ビューポート単位はブラウザの表示領域のサイズに相対的
1vw=画面の幅1%、1vh=画面の高さ1%
サイズが比例してスケーリング and 最小値を確保する
code:css
:root {
font-size: calc(1rem + 0.5vw);
}
em
周辺のコンテキストに基づいて設定できる機能
code:css
h2 {
font-size: 2.5rem;
}
h2 strong {
font-size: 1.125em; // 1.125 * 2.5rem になる
}
em = インライン要素、rem = ブロック要素のサイズ設定に適している
例えばSVGはem基準に最適。アイコンはテキストに付随したりするので。
ch(0の幅)とex(xの高さ) = 特定の文字の幅または高さに関係している。
カラム幅を一定に保つなどに使える。
code:css
h2,h3 {
max-width: 60ch;
}
h3 {
font-size: 2rem;
}
h2 {
font-size: 2.5rem
}
CSSレイアウトにおけるエラー = コンテンツが崩れる・読めなくなること
グローバルスタイルとローカルスタイル
グローバルなCSS規則を適切に活用することブランディングや美観をレイアウトから分離できる。いわゆる関心の分離ができる。
ユーティリティクラス
CSSの宣言構造にそっくりそのままの命名規則でルートに配置
code:css
root {
--font-size-base: 1rem;
--font-size-biggish: 1.75rem;
--font-size-big: 2.25rem;
}
.font-size\:base {
font-size: var(--font-size-base) !important;
}
.font-size\:biggish {
font-size: var(--font-size-biggish) !important;
}
.font-size\:big {
font-size: var(--font-size-big) !important;
}
過度な使用は避ける
このユーティリティクラスを極限まで突き詰めるとtailwindになる
ローカルスタイルと「スコープ化されたスタイル
idセレクタ・インラインセレクタ・shadow dom、どれも詳細度が高過ぎてグローバルなスタイル設定が活用できない
プリミティブとprops
レイアウトプリミティブは普通のスタイルとユーティリティクラスの中間に位置する
デフォルト
スタックのプリミティブの場合の例。カスタムコンポーネントにしてpropsを差し込む例があったが省略。
code:css
stack-l {
display: block;
}
stack-l > * + * {
margin-top: var(--s1);
}
モジュラースケール
視覚的な調和
モジュラースケール
code:txt
1 * 1.5; // 1.5
1.5 * 1.5; // 2.25
1.5 * 1.5 * 1.5; // 3.375
デザインにも調和を生み出す
カスタムプロパティ
カスタムプロパティとcalc()を使うとcssでもモジュラースケールを表現できる。
code:css
:root {
--ratio: 1.5;
--s-5: calc(var(--s-4) / var(--ratio));
--s-4: calc(var(--s-3) / var(--ratio));
--s-3: calc(var(--s-2) / var(--ratio));
--s-2: calc(var(--s-1) / var(--ratio));
--s-1: calc(var(--s0) / var(--ratio));
--s0: 1rem;
--s1: calc(var(--s0) * var(--ratio));
--s2: calc(var(--s1) * var(--ratio));
--s3: calc(var(--s2) * var(--ratio));
--s4: calc(var(--s3) * var(--ratio));
--s5: calc(var(--s4) * var(--ratio));
}
選択した比率の徹底によって調和が生み出される
公理
公理に基づいていないと調和の取れたデザインができない。デザインの公理をシステム全体に浸透させたい。タイポグラフィのカラム幅を例に説明
カラム幅
テキスト行の長さ(文字数)のことをカラム幅と呼ぶ
45~75文字(日本語では24~40文字程度)がカラム幅として理想
カラム幅の公理
公理: カラムは60chを超えてはいけない
公理は全体に行き渡らせないのでユニバーサルスタイルとして定義
これを実現するにはch(0の文字幅)を使うと良い
目で見ずにデザインする
Webのためにデザインすることは、目に見える成果物を作るのではなく、目に見える成果物を生成するための「 計画 」を記述する行為
グローバルなデフォルト
クラスだと各要素に全てクラスを指定しないといけなくなるので要素に適用
例外ありきのスタイル設定
code:css
*{
max-width: 60ch;
}
html,
body,
div,
header,
nav,
main,
footer {
max-width: none;
}
合成レイアウトにおけるカラム幅
「公理」の多くはレイアウトには影響しないのでグローバルスタイルで適用する
レイアウトを考慮するときはあらゆる構成や向きについて慎重になること
Chapter 2
以降、個別のレイアウトの解説が続く。
Stack
ダメな例
コンテキストを考慮できていない。マージン設定が適切になるかどうかが場合による。last-childに不要なmarginが入ったりする。
code:css
p {
margin-bottom: 1.5rem;
}
解決策
コンテキストに対してスタイルを設定
+を使うと要素の前に別の要素が存在する時のみmargin-topが適用される
* + *はフクロウセレクタと呼ばれている
code:css
.stack > * + * {
margin-top: 1.5rem;
}
入れ子
もっと複雑な入れ子
code:css
margin-top: 0;
margin-bottom: 0;
}
.stack-large > * + * {
margin-top: 3rem;
}
.stack-small > * + * {
margin-top: 0.5rem;
}
Stackの中の区分け
Flexコンテキスト
code:css
stack {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.stack > * + * {
margin-top: var(--space, 1.5rem);
}
.stack > :nth-child(2) {
margin-bottom: auto;
}
Box
Every Layoutは全てボックスの組み合わせ
Stack には垂直方向のマージンを挿入する以外の役割を与えない
font-familyやcolorやline-heightのようなスタイル設定を個別に行うのは無駄。Boxでは基本的に行わない。
グローバルスタイルの多くは「 ブランディング」に関わるスタイル。ほとんど美観に影響するスタイルである。よって要素の構造には影響はほぼない。
解決策
幅と高さは外因的な値(flex-basisとflex-grow、flex-shrinkの組み合わせにより計算される幅など)やBox の内側のコンテンツから自然に導かれるべき
入るものがなければBoxは不要。入るものがあればピッタリのスペースを持ったBox。
padding
padding(borderも)はBoxの中の要素に作用するので使っても良いがpaddding-topなどの特定の指定は控える。コンテンツを縁から離すことのみに専念。
background
code:css
.box {
color: var(--color-dark);
background-color: var(--color-light);
padding: var(--s1);
}
.box * {
color: inherit;
}
.box.invert {
color: var(--color-light); background-color: var(--color-dark);
}
Center
ほとんどのコンテンツで段落を組む場合中央揃えはお勧めできない。読むづらくなるので。
解決策
marginのautoを使う。max-widthは基本的にch単位で設定すべき。
code:css
.center {
max-width: 60ch;
margin-left: auto;
margin-right: auto;
}
60chを維持しつつpaddingでコンテンツが小さくなるのを防ぐ
code:css
.center {
box-sizing: content-box;
max-width: 60ch;
margin-left: auto;
margin-right: auto;
padding-left: var(--s1);
padding-right: var(--s1);
}
内在的な中央揃え
margin:autoにdisplay: flexも追加する
code:css
.center {
box-sizing: content-box;
max-width: 60ch;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
align-items: center;
}
Cluster
要素のグループ
フォームの末尾にまとめて表示されるボタンの組み合わせや、 タグやキーワード、その他のメタ情報のリストなど。
問題
テキストをグリッドのようにレイアウトを組みたい場合、inline-blockを使う方法があるが、それだと余計なスペース文字が表示されて崩れる。
解決策
親をフレックスボックスにする。
code:css
.cluster {
display: flex;
flex-wrap: wrap;
}
マージンの追加と隠蔽
親要素に負のマージンを適用して子要素を親要素の縁に引き寄せる
code:css
.cluster {
--space: 1rem;
}
.cluster > * {
display: flex;
flex-wrap: wrap;
gap: var(--space, 1rem);
}
位置揃え
justifiy-content
Sidebar
問題
端末ごとなどの違いを考慮した横並びのレイアウト難しすぎる問題。横幅がある時は横並びに、横幅がない時は縦並びになるようなレイアウト(クオンタムレイアウト。量子の重ね合わせ状態)
解決策
code:css
.with-sidebar {
display: flex;
flex-wrap: wrap;
gap: 1rem; /*これを入れるとブロック間でmarginを入れられる*/
}
.sidebar {
flex-basis: 20rem; /*これを設定しなければコンテンツのサイズに等しくなる*/
flex-grow: 1;
}
.not-sidebar {
flex-basis: 0;
flex-grow: 999;
min-width: 50%;
}
Switcher
問題
水平方向に同じ比率で占めるようにできない
解決策
code:css
.switcher {
display: flex;
flex-wrap: wrap;
gap: var(--gutter, var(--s1));
--threshold: 30rem;
}
.switcher > * {
flex-grow: 1;
flex-basis: calc((var(--threshold) - 100%) * 999);
}
.switcher > :nth-last-child(n+5),
.switcher > :nth-last-child(n+5) ~ * {
flex-basis: 100%;
}
Cover
問題
垂直方向に可変する(もしくは縦幅より大きい)コンテンツを中央揃えするの難しい
解決策
code:css
.cover {
--space: var(--s1);
display: flex;
flex-direction: column;
min-height: 100vh;
padding: var(--space);
}
.cover > * {
margin-top: var(--space);
margin-bottom: var(--space);
}
.cover > :first-child:not(h1) {
margin-top: 0;
}
.cover > :last-child:not(h1) {
margin-bottom: 0;
}
.cover > h1 {
margin-top: auto;
margin-bottom: auto;
}
code:html
<div class="cover">
<div><!-- 最初の子要素 --></div>
<h1><!-- 中央揃えにされる子要素 --></h1>
<div><!-- 最後の子要素 --></div>
</div>
Grid
問題
グリッドレイアウトは動的にサイズが変わるwebにおいては至難の業
解決策
code:css
.grid {
display: grid;
grid-gap: 1rem;
--minimum: 20ch;
}
@supports (width: min(var(--minimum), 100%)) { /* min(A,B)はBとAの小さい方を返すやつ */
.grid {
grid-template-columns: repeat(auto-fit, minmax(min(var(--minimum), 100%), 1fr));
}
}
Frame
問題
幅と高さの値をハードコーディングすることなくアス比を維持してコンテナ内に画像を表示する
解決策
code:css
.frame {
--n: 9; /* 高さ */
--d: 16; /* 幅 */
padding-bottom: calc(var(--n) / var(--d) * 100%); position: relative;
}
.frame > * {
overflow: hidden;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
.frame > img, .frame > video {
width: 100%;
height: 100%;
object-fit: cover;
}
code:html
<div class="frame">
<img src="/path/to/image" alt="画像の説明">
</div>
Reel
問題
ブロックのフロー方向を効率的に変更する
解決策
考慮すべきこと多すぎる...
code:html
<div class="reel">
<div><!-- 子要素 --></div>
<div><!-- 子要素--></div>
<div><!-- 子要素 --></div>
<div><!-- 子要素 --></div>
</div>
code:css
.reel {
--space: 1rem;
--reel-height: auto;
--item-width: 25ch;
display: flex;
height: var(--reel-height);
overflow-x: auto;
overflow-y: hidden;
scrollbar-color: var(--color-light) var(--color-dark);
}
.reel::-webkit-scrollbar {
height: 1rem;
}
.reel::-webkit-scrollbar-track {
background-color: var(--color-dark);
}
.reel::-webkit-scrollbar-thumb {
background-color: var(--color-dark);
background-image: linear-gradient(
var(--color-dark) 0,
var(--color-dark) 0.25rem,
var(--color-light) 0.25rem,
var(--color-light) 0.75rem,
var(--color-dark) 0.75rem
);
}
.reel > * {
flex: 0 0 var(--item-width);
}
.reel > img {
height: 100%; flex-basis: auto; width: auto;
}
.reel > * + * {
margin-left: var(--space);
}
.reel.overflowing:not(.no-bar) {
padding-bottom: var(--space);
}
.reel.no-bar {
scrollbar-width: none;
} 10
.reel.no-bar::-webkit-scrollbar {
display: none;
}
code:js
(function() {
const className = 'reel';
const reels = Array.from(
document.querySelectorAll(.${className})
);
const toggleOverflowClass = elem => {
elem.classList.toggle(
'overflowing',
elem.scrollWidth > elem.clientWidth
);
}
for (let reel of reels) {
if ('ResizeObserver' in window) {
new ResizeObserver(entries => {
for (let entry of entries) {
toggleOverflowClass(entry.target);
}
}).observe(reel);
}
if ('MutationObserver' in window) {
new MutationObserver(entries => {
for (let entry of entries) {
toggleOverflowClass(entry.target);
}
}).observe(reel, {childList: true});
} }
})();
Imposter
問題
他の要素に重ね合わせつつ中央配置するような場合
解決策
code:css
.imposter {
position: var(--positioning, absolute);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.imposter.contain {
--margin: 0px;
overflow: auto;
max-width: calc(100% - (var(--margin) * 2));
max-height: calc(100% - (var(--margin) * 2));
}
Icon
レイアウトコンポーネントで唯一のインライン要素
問題
アイコンとテキストの間のスペース
アイコンの高さとテキストの高さの関係性
アイコンとテキストの垂直方向の位置揃え
テキストがアイコンの前ではなく後ろに来る場合はどうなるか
テキストのサイズを変更するとどうなるか
解決策
code:html
<span class="with-icon">
<svg class="icon">
<use href="/path/to/icons.svg#cross"></use>
</svg>
Close
</span>
code:css
.icon {
height: 0.75em;
height: 1cap;
width: 0.75em;
width: 1cap;
}
.with-icon {
display: inline-flex;
align-items: baseline;
}
.with-icon .icon {
margin-inline-end: var(--space, 0.5em);
}
その他