Trelloボード画面(例はこちら)の基本的なレイアウトの実装方法を紹介します。レスポンシブでCSSのみを使うソリューションでレイアウト構造の特徴を解説します。
最終結果のCodePenデモです。
Grid LayoutとFlexboxのほかに、calcとviewportユニットを使います。読みやすいコードで効率を上げるため、Sass変数も使います。
フォールバックは提供しません。コードは対応するブラウザーで走らせます。前置きはここまでにして、本題に入ります。画面コンポーネントを1つ1つ開発します。
画面レイアウト
Trelloボード画面は、アプリバー、ボードバー、カードリストのセクションで構成されます。この構造を実装するため、次のマークアップスケルトンを使います。
<div class="ui">
<nav class="navbar app">...</nav>
<nav class="navbar board">...</nav>
<div class="lists">
<div class="list">
<header>...</header>
<ul>
<li>...</li>
...
<li>...</li>
</ul>
<footer>...</footer>
</div>
</div>
</div>
レイアウトの実現には、CSS Gridを使います。具体的には、3×1のグリッド(3行1列)で、最初の行はアプリバー、2行目はボードバー、3行目は.lists要素用です。
最初の2行の高さは固定して、3行目は残りのビューポートの高さにします。
.ui {
height: 100vh;
display: grid;
grid-template-rows: $appbar-height $navbar-height 1fr;
}
ビューポートの単位により、.uiコンテナの高さは常にブラウザーのビューポートと同じになるように保証されます。
グリッドフォーマットコンテクストがコンテナに割り当てられ、上で指定されたグリッド行と列が定義されます。正確には、定義されるのは行だけで、列は1列だけなので宣言する必要はありません。行の大きさは、バーの高さとfrユニットにSass変数をいくつか使い決定し、.lists要素の高さを残りのビューポート高さにします。
カードリストセクション
画面グリッドの3行目にはカードリストのコンテナが入ります。マークアップのアウトラインを示します。
<div class="lists">
<div class="list">
...
</div>
...
<div class="list">
...
</div>
</div>
リストのフォーマットに、ビューポートの幅全体にわたるFlexboxの1行コンテナを使います。
.lists {
display: flex;
overflow-x: auto;
> * {
flex: 0 0 auto; // 'rigid' lists
margin-left: $gap;
}
&::after {
content: '';
flex: 0 0 $gap;
}
}
overflow-xプロパティにautoを割り当てると、用意したビューポートの幅にリストが収まらない場合、ブラウザーはスクリーンの下部に水平スクロールバーを表示します。
リストをリジッドにするためにflexショートハンドプロパティをflexアイテムに用います。(ショートハンドで使われる)flex-basisにautoを割り当てると、レイアウトエンジンは.list要素の幅プロパティから大きさを読み取り、flex-growとflex-shrinkをゼロにすることで、幅が変動しません。
次に、リスト間に水平方向の間隔を取ります。リストの右わきの間隔を設定すると、水平方向に伸びる最後のリストのマージンが無視されます。修正するにはリストを左側のマージンで分離します。最後のリストとビューポートの右端の間隔は、各.lists要素に::after疑似要素を加えて処理します。デフォルトのflex-shrink: 1をオーバーライドします。疑似要素がマイナスのスペースをすべて吸収して消えるのを防ぎます。
Firefox 54以前は、正しいレイアウトを保証するため、明示的に.listsにwidth: 100%を指定します。
カードリスト
それぞれのカードリストは、ヘッダーバー、カードの並び、フッターバーからなります。HTMLスニペットはこの構造です。
<div class="list">
<header>List header</header>
<ul>
<li>...</li>
...
<li>...</li>
</ul>
<footer>Add a card...</footer>
</div>
リストの高さの管理が重要です。ヘッダーとフッターの高さは固定します(同じ高さだとは限りません)。カードの枚数が変わり、1枚のカード中のコンテンツの量も変わるので、カードを加えたり、削除したりするとリストは垂直方向に伸びたり縮んだりします。
高さは無限に伸びるわけではなく、上限があります。.lists要素の高さに依存します。上限になってリストに入りきれなかったカードにアクセスするため、垂直方向のスクロールバーを作ります。
max-heightとoverflowプロパティで実装できそうですが、これらのプロパティはルートコンテナである.listに適用されるので、リストが最大の高さに達したら、スクロールバーはヘッダーとフッターも含めたすべての.list要素に表示されます。左図が間違ったサイドバー、右図が正しいサイドバーです。
そこで、max-height制約を内側の<ul>に適用します。リスト親コンテナ(.lists)の高さからヘッダーとフッターの高さを差し引いた値を設定します。
ul {
max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});
}
.listsを参照せず、<ul>要素の親、.listを参照しますが、要素がはっきりとした高さを持たないので百分率は求められません。この問題は、.listを.listsと同じ高さにすることで解決できます。
.list {
height: 100%;
}
これで、.listはコンテンツに関わらず.listsと同じ高さです。リストの背景色にbackground-colorプロパティは使えませんが、その子要素(ヘッダー、フッター、カード)は使えます。
リストの下端とビューポートの下端の間のスペース($gap)の高さも調整します。
.list {
height: calc(100% - #{$gap} - #{$scrollbar-thickness});
}
リストが.list要素の水平スクロールバーに触れるのを防ぐため、$scrollbar-thicknessだけ差し引きます。Chromeではこのスクロールバーは.listsボックスの中で「成長」、すなわち100%は、スクロールバーを含めた.listsの高さになります。
一方Firefoxでは、スクロールバーは.listsの高さに「付加」されます。すなわち100%はスクロールバーを含まない.listsの高さです。従って、引き算は不要です。結果、スクロールバーが見えているときは、最大の高さに達したリストの下端とスクロールバーの上端との間に見えるスペースがFirefoxは少し大きくなります。
このコンポーネントに対応するCSSのルールです。
.list {
width: $list-width;
height: calc(100% - #{$gap} - #{$scrollbar-thickness});
> * {
background-color: $list-bg-color;
color: #333;
padding: 0 $gap;
}
header {
line-height: $list-header-height;
font-size: 16px;
font-weight: bold;
border-top-left-radius: $list-border-radius;
border-top-right-radius: $list-border-radius;
}
footer {
line-height: $list-footer-height;
border-bottom-left-radius: $list-border-radius;
border-bottom-right-radius: $list-border-radius;
color: #888;
}
ul {
list-style: none;
margin: 0;
max-height: calc(100% - #{$list-header-height} - #{$list-footer-height});
overflow-y: auto;
}
}
リストの背景色は$list-bg-color値を各.list要素の子のbackground-colorプロパティに割り当てることで表示されます。overflow-yは、カードスクロールバーを必要なときだけ表示します。最後に、ヘッダーとフッターを簡単にスタイリングします。
仕上げ
カード1枚のHTMLは、リストアイテムから構成されます。
<li>Lorem ipsum dolor sit amet, consectetur adipiscing elit</li>
カードにカバー画像がある場合です。
<li>
<img src="..." alt="...">
Lorem ipsum dolor sit amet
</li>
対応するCSSです。
li {
background-color: #fff;
padding: $gap;
&:not(:last-child) {
margin-bottom: $gap;
}
border-radius: $card-border-radius;
box-shadow: 0 1px 1px rgba(0,0,0, 0.1);
img {
display: block;
width: calc(100% + 2 * #{$gap});
margin: -$gap 0 $gap (-$gap);
border-top-left-radius: $card-border-radius;
border-top-right-radius: $card-border-radius;
}
}
背景、パディング、ボトムマージンの設定が終わったら、次はカバー画像のレイアウトです。画像の幅は左のパディング端から右のパディング端まで広げます。
width: calc(100% + 2 * #{$gap});
画像を水平方向および垂直方向に揃えるため、負のマージンを割り当てます。
margin: -$gap 0 $gap (-$gap);
3番目の正のマージンの値でカバー画像とカードテキストの間のスペースを設定します。
画面レイアウトの最初の行を占める2本のバーにflexフォーマットコンテキストを加えました。ここで作ったのは簡単なものですので、デモを拡張してオリジナルレイアウトを作ってください。
最後に
紹介したアプローチは、このデザインを実現するための1つの方法に過ぎません。2つのスクリーンバーを完成したり、レイアウトを確定させたり、ほかにもおもしろいアプローチがありそうです。
カードリストに専用のスクロールバーを設けるのもいいでしょう。
(原文:Building a Trello Layout with CSS Grid and Flexbox)
[翻訳:関 宏也/編集:Livit]