[Reveal.js] CSS Grid LayoutでMarkdownにもレイアウトを

# Reveal.jsとは
[Reveal.js](https://github.com/hakimel/reveal.js)とは、HTMLによるプレゼンテーションのスライドを簡単につくることができるフレームワークです。どんな雰囲気かは[公式のデモ](https://revealjs.com/#/)を見るのがわかりやすいと思います。
Reveal.jsを使うと、Markdownでスライドを作成することができ、作業効率が大きくあがるのでとてもオススメです（参考：Qiita記事「[reveal.js+Markdown](https://qiita.com/siguremon/items/c717eca388070215712c)」）。

# Markdownでもレイアウトを指定するぞ
しかし、Markdownでスライド書くと、コンテンツ表示位置の調整は簡単ではありません。例えば、見出しは一番上に固定したいとか注釈はフッター位置に表示したいとかの場合、個別にCSSを頑張るのは結構大変な作業でした。
そこで、いま人気のCSS Grid Layoutを利用して、Markdownでもレイアウトを指定できるようにしてみました。例えば、下記のようなマークダウンを書くと..

```md
### HOCよりもRender Props

.react-routerのmjackson氏が推奨<br >データの流入元が明示的


~~{:main}~~      

###### コンポーネント定義例
```javascript
const ParentComponent extends Component {
    constructor() {
        this.state = { value: 1 };
    }
    render () {
        return (<div> {props.render(this.state)} </div>);
    }
)
    
const ChildComponent = (props) => (
    <ParentComponent render={(value)=> { <button>{value}</button> } />);
``｀
~~x~~
    
~~{:footer}~~    
HOC: Higher Order Componentの略
~~x~~
```

こんなかんじに変換されるし、

<img width="324" alt="スクリーンショット 2018-01-11 0.30.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/160451/71897bf2-52b7-f55f-62c1-78f0d231137b.png">

次のようなMarkdownを書くと..

```md
### オニオンアーキテクチャ
    
.Jeffrey Palermo氏が提唱したアーキテクチャ
<br>外側の層から内側の層のみ依存関係
    
~~{:left}~~

.特徴 :mag:
 - ドメインモデルは完全独立
   * インフラの寿命は３年
 - 依存性逆転の法則(DIP)
   * 内: インタフェースを定義
   * 外: 具象を実装
~~x~~

~~{:right}~~
![arch{90%,80%}](onion.png)
~~x~~

~~{:footer}~~
 画像出典: [ドメイン駆動 + オニオンアーキテクチャ概略](https://qiita.com/little_hand_s/items/2040fba15d90b93fc124)
~~x~~
```
<img width="325" alt="スクリーンショット 2018-01-11 0.34.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/160451/b9285fc7-425d-1999-8c29-4726dfbdda90.png">

こんな感じの２カラムレイアウトになります。

# 特徴
標準のMarkdownと違うのは以下のとおりです。

 * `~~{:レイアウト位置}~~`と`~~x~~`で囲まれた箇所が指定位置に表示される ←**本記事のメイン**
 * 見出しとかリード文が適切な固定位置に表示される
 * ``![alt{幅, 高さ}](画像のURL)``で画像のサイズを指定できる
 * ``:smile:``とかで絵文字を表示できる


# やり方
## CSS Grid Layout

レイアウトにはCSS Grid Layoutを利用しています。Grid Layoutの説明はQiita記事「[CSS Grid Layout を極める！](https://qiita.com/kura07/items/e633b35e33e43240d363)」がわかりやすいので、Grid Layoutってなに？ってかたはご参照ください。すごく簡単に説明すると、画面をグリッド状に分割して、グリッドで位置指定する方式です。今回はグリッドで分割して下記のようなエリアを作成しております。

<img width="318" alt="grid.png" src="https://qiita-image-store.s3.amazonaws.com/0/160451/eed9018a-e05f-d05c-84b7-648df1676cdd.png">


こんな感じのCSS設定です。

~~~css
.grid-container {
    display: grid !important;
    grid-template:
        "header header  header header" 100px
        " ...    lead    lead   ...  " 150px
        " ...    main    main   ...  " 1fr
        " ...    footer footer  ...  " 50px
        / 50px     1fr    1fr   50px;
} 
/* クラス名とエリアの対応付け */
.header { grid-area: header; }                               
.lead { grid-area: lead; }
.full { grid-area: 2/1 / 4/5; }                             
.main { grid-area: main; }
.left  { grid-area: 3 / 2; }
.right { grid-area: 3 / 3; }
.footer { grid-area: footer; }
~~~


## レイアウト位置の指定

複数の要素を１つのグリッドエリアに表示するには、div要素などで囲んで構造化する必要があります。Markdownで書いた要素はすべてフラットになるため、なんとか構造化しなければなりません。
Markdown内にはHTMLタグも直接も書けるので、試しに次のようにかいてみましたが、`<div>`内はマークダウンとしては解釈されず、文字列として扱われてしまいうまくいきません:cry:

```md
<div>
 * ここは
 * マークダウンとしては
 * __解釈されないよ__
</div>
```

そこで、Reveal.jsのMarkdownプラグインをほんのちょっとだけ拡張することにしました。

## Markdownプラグインを拡張する
Markdownプラグインは内部で[marked](https://github.com/chjj/marked)というMarkdownをHTMLに変換するライブラリを利用しています。このmarkedは、MarkdownからHTMLへの変換ロジックをカスタマイズする機能を提供しているので、これを利用して`<div>`で囲って任意のクラス名を付与できるようにしました。
カスタマイズするMarkdownは使用頻度が低く、見た目も区切り文字っぽい取り消し線（del要素、Markdownでは`~~`)にしました。拡張の方法は下のような感じです。

```javascript
const renderer = new root.marked.Renderer();

// del 要素のレンダラーを拡張
renderer.del = function(text) {
    let out = `<del>${text}</del>`;
    const isDivStart = text.match(/^\s*{:(.*)}$/);
    if (isDivStart) out = `<div class="${isDivStart[1]}">`;
    if (text == 'x') out = `</div>`;
    return out;
}

root.RevealMarkdown = factory(root.marked, renderer);
```

取り消すテキストが`{:エリア名}`にマッチすれば、テキストの取り消しではなく、エリア名をクラス名に持つdiv要素を挿入するようにしました。

しかし、実はこれだけではうまくいきません。delなどのインライン要素は勝手にp要素で囲まれてしまうのです。

```
~~~{:main}~~~

こんにちわ

~~x~~
```
などと書いても、変換後のHTMLは

```html
<p><div class="main"></p>
<p>こんにちわ</p>
<p></div></p>
```
と、意味不明な感じになってしまいます。そこで、苦肉の策ではありますが、先頭がピリオドから始まる時のみp要素としてタグを付加し、それ以外はタグを付けないようにレンダラを拡張しました。

```js
renderer.paragraph = function(text) {
    const isParagraph = text.match(/^(\.\s*)/);
    let out = text; 
    if (isParagraph) {
        text = text.replace(isParagraph[1],'');
        out = `<p>${text}</p>\n`;
    }
    return out;
}
```

p要素にしたいときは割り切って、先頭にピリオドをつけましょう。

# その他
## デフォルト表示位置
html要素の種類に応じてデフォルトのレイアウト位置を決めてます。例えばh1要素はheaderに、p要素はleadに表示する、などとしました。単一要素ならば`~~{:レイアウト位置}~~`が省略できて楽ですね。

## 画像サイズの指定
こちらもmarkedのレンダラーを下のようにカスタマイズし、widthとheightのスタイルを指定できるようにしています。

```js
renderer.image = function(href, title, text) {
    if (this.options.baseUrl && !originIndependentUrl.test(href)) {
        href = resolveUrl(this.options.baseUrl, href);
    }
    const size = text.match(/{(.+),(.+)}/);
    if (size) {
        text = text.replace(size[0], '');
    }
    var out = '<img src="' + href + '" alt="' + text + '"';
    if (title) {
        out += ` title="${title}"`;
    }
    if (size) {
        out += ` style="width:${size[1]}; height:${size[2]}"`;
    }
    out += this.options.xhtml ? '/>' : '>';
    return out;
};
```

## 絵文字の利用
こちらに[別途記事](https://qiita.com/vimyum/items/3de00082d96a4a1e3327)を書いております。


以上、修正したmarkdownプラグインと、CSSファイルは[こちらのGist](https://gist.github.com/vimyum/bb7c0f741a92ae01574120e5ee1bb798)に貼り付けておきます。`./plugin/markdown/markdown.js`を上書きしてお使いください。
それでは爆速のMarkdownライフを:zap: