Qiita の新記事ページのレイアウト実装

この投稿は [Increments Advent Calendar 2017](https://qiita.com/advent-calendar/2017/increments) の5日目の記事です。3日目の @htomine に続き、記事ページのデザインについて説明する予定でしたが、[リリースブログ](http://blog.qiita.com/post/168173551204/qiita-design-update) と内容がかぶるので、この記事では記事ページのデザイン実装について述べます。

Qiita の記事ページで利用した CSS の比較的新しい機能は以下の2つです。

- CSS Grid Layout Module
- `position: sticky`

これらの紹介と、Qiita での使い方を解説します。

## CSS Grid Layout Module

CSS Grid Layout Module (以下 CSS Grid) とはその名の通り、CSS でグリッドレイアウトを実現する仕様です。`display: grid` という `display` プロパティに新しい値が追加され、その宣言がされている要素にグリッドレイアウト情報を定義します。そして、その子要素にはグリッドのどこにレイアウトされるのかを定義します。

2017年12月現在、各ブラウザでの実装状況は以下の通りです。IE が[古い仕様](https://www.w3.org/TR/2011/WD-css3-grid-layout-20110407/) の実装となっていますが、モダンブラウザでは利用可能です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6598/69ccd531-3247-9dc4-722f-709dfde99015.png)


CSS Grid についての詳細は以前 Qiita に記事を投稿したので、そちらを参照下さい。

https://qiita.com/morishitter/items/738488290451555d913c

## `position: sticky`

`position` プロパティは1997年頃、ちょうど [Wired](https://www.wired.com/) のようなメディアがウェブの上に載り始めたころ[提案](https://lists.w3.org/Archives/Public/www-style/1997Jan/0081.html)されました。当時 negative margin だけでは表現しきれないレイアウトを実現するために生まれました。

凝ったレイアウトをするには必須である `position` プロパティに新しく `sticky` という値が追加されています。`position: sticky` を利用することで、これまで JavaScript を使って表現していた、スクロールに追従してスクリーンに表示されるレイアウトをすることが可能です。

```css
.sticky-element {
  position: sticky;
  top: 0;
}
```

上記のようなスタイルを指定された要素は、その親要素がスクリーンに表示されている間、スクリーン上部 (`top: 0`) に固定され表示し続けます。

`position: sticky` のブラウザサポートは以下のようになっています。IE を除くブラウザで利用可能です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6598/5eafd830-9770-a20d-b3bd-c062407e7629.png)


## Qiita での利用

Qiita ではログインしていないユーザーに対して、記事ページに広告を表示しています。（ログインしたら消えます！）広告はスクロールに追従させず、その下にある目次と左側の「いいね」「ストック」ボタンを追従させるという UI 要件を解決する方法として、CSS Grid と `position: sticky` を利用しています。

<a href="https://gyazo.com/8dea3f03b3e0adc34188ff4e7c3cfaae"><img src="https://i.gyazo.com/8dea3f03b3e0adc34188ff4e7c3cfaae.gif" alt="https://gyazo.com/8dea3f03b3e0adc34188ff4e7c3cfaae" width="960"/></a>



Qiita の記事部分 (背景色がグレーの部分) はざっくり以下のような HTML 構造になっています。

```html
<div class="container"> <!-- 記事部分全体 (背景色がグレー) -->

  <!-- 左側に表示される「いいね」「ストック」ボタン -->
  <div class="stickyMenu"></div>

  <!-- 記事部分 (背景色が白) -->
  <div class="main"></div>

  <!-- オプショナルなエリア (非ログイン時は広告が出る) -->
  <div class="options"></div>

  <!-- 目次 (Table of Contens) -->
  <div class="toc"></div>
</div>
```

このうち、stickyMenu と toc は記事を読んでいる間（記事部分全体の要素がスクリーンに表示し続ける間) 固定して表示し続けます。そのために stickyMenu と toc に `position: sticky` を指定し、その親要素は固定表示させたいエリア (container) にする必要があります。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/6598/469cd293-bf7c-546b-5043-5d7de08d6ef8.png)

この条件の元、上記の画像のようなレイアウトをするために CSS Grid を利用します。

```css
.container {
  display: grid;
  grid-template-column:
    $left-sidebar-width
    1fr
    $right-sidebar-width;
  grid-template-rows: minmax(300px, auto) 1fr;
}
```


container をグリッドコンテナとして、2行3列のグリッドを定義します。そして、子要素のグリッドアイテムをトラックに配置していきます。

```css
.stickyMenu {
  position: sticky;
  top: 0;
  
  grid-column: 1 / 2;
  grid-row: 1 / 2;
}

.main {
  grid-column: 2 / 3;
  grid-row: 1 / 2;
}

.option {
  grid-column: 3 / 4;
  grid-row: 1 / 2;
}

.toc {
  position: sticky;
  top: 0;

  grid-column: 3 / 4;
  grid-row: 2 / 3;
}
```


といった実装になっています。CSS Grid は浅い DOM 構造で無茶ができるので、 `position: sticky` などと相性が良いですね。CSS のモダンなレイアウトをする際の参考になれば幸いです。