読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

ElectronでCSS Grid Layout Moduleを使ってちょっと未来のレイアウト方法を先取りしてみる

Electron Node.js VSCode

この記事はElectron Advent Calendar 2015 - Qiita2015の8日目の記事です。

今回の記事のサンプルコードは↓コチラ。
https://github.com/sourcechord/electron-gridlayout-sample

目次

前置き

CSSでのレイアウトって難しいですよね。。。

自分は今まで主に.net系のデスクトップアプリ開発をやってきましたが、ここ最近Web系の開発に手を出してみて、色々と勝手の違うCSSでのレイアウトにずいぶんと苦労しました。

XAML系のプログラミングだったらGridパネル、Androidの開発でのGridLayoutというような、いい感じにグリッドレイアウトを実現するためのものが現在のCSSにはありません。

なんで、よくあるこういうHoly Graylレイアウトを作るのにこんな苦労しなきゃいかんのか・・・と、ずいぶん頭を抱えてきました。

CSS Grid Layout Module Level 1

ですが、CSSでもグリッドレイアウトを定義するための仕様の策定が進んでいます。
CSS Grid Layout Module Level 1

CSS Grid Layout Module Level 1」では、平面上をグリッドに分割して、任意の位置に要素を配置をしていくことができます。
この手のレイアウトはデスクトップアプリのようなUIのレイアウトととても相性がいいです。

また、MediaQueryと組み合わせてレスポンシブにUIのレイアウトを切り替えるのも行いやすく、まさにElectronでアプリ作る時にはバッチリ相性のよいレイアウト方法だと思います。

CSS Grid Layoutの各ブラウザの対応状況

この「CSS Grid Layout Module Level1」ですが、2015年12月現在の対応状況は以下のような状態です。
f:id:minami_SC:20151208233257p:plain:w350
Can I use... Support tables for HTML5, CSS3, etc

Webサイトの作成などでは、当然使えるわけない!!って状況ですね。

対応状況について補足
  • IE10以降やEdgeは部分的な対応となっていますが、以下のような状況です
    • -ms-のベンダープレフィックスが必要
    • ↓の古い仕様への対応(現在策定中の仕様とは少し異なるもの)
      Grid Layout
  • ChromeFirefoxでは、拡張フラグを指定することで、実験的な機能として利用できます。
    • Chromeで使うためには
      現在Chromeは、標準ではこのレイアウトに対応していませんが、拡張フラグをONにすることで、実験的な機能として有効になります。
      f:id:minami_SC:20151208233319p:plain:w350

Chromiumを内部で使用するElectronのアプリでも、同様のフラグをONにすることでこの機能が使えるようになります。

メリット・デメリット?

現時点で標準になっていない機能(まだWorkingDraftの段階)なので、もちろんデメリットやリスクもあったりすると思います。
今後仕様が変わったりしても追従していく覚悟も必要ですし、まだ実装されていない部分や、不具合に遭遇する可能性も多々あると思います。
それに、学習するための情報もまだまだ少なめです。

ですが、レイアウトがこれだけシンプルで自由自在にできるようになるので、デメリットやリスクを補って余りある十分なメリットがあるのでは、という気がしています。

CSS Grid Layoutを使ってみる

参考サイト

CSS3 Grid Layoutの使い方については、下記ページでサンプルを交えて非常に詳しく説明してくれてます。
Grid by Example
今回はこれを参考に色々やってみます。

言葉の定義

まずは、Gridレイアウトで出てくる言葉の定義についてザックリと解説します。
Gridの中の、行や列/セルを表す単位は以下のようになっています。

  • Grid Line
    この図で赤い線を引いている部分は、column 2のLineとなります。
    f:id:minami_SC:20151208233339p:plain:w350

  • Grid Track
    Grid中の1列or1行をまとめて指す単位がTrackです。
    f:id:minami_SC:20151208233348p:plain:w350

  • Grid Cell
    Grid中の特定の1コマを指定する単位がCellです。
    f:id:minami_SC:20151208233357p:plain:w350

  • Grid Area
    このように、複数のセルを矩形にまとめたGrid中の特定の領域を示す単位です。
    f:id:minami_SC:20151208233406p:plain:w350

Chromiumの拡張フラグを設定する

Electronでは、以下のappendSwitchというメソッドChromiumで使用する拡張フラグの設定ができます。
http://electron.atom.io/docs/v0.35.0/api/app/#app-commandline-appendswitch-switch-value

このメソッドを使って「試験運用版のウェブプラットフォームの機能」のオプションを有効にします。
以下のメソッドをMainプロセスのスクリプトに追記します。

App.commandLine.appendSwitch("--enable-experimental-web-platform-features");

これで準備OK。

単純なグリッドレイアウト

実際にGridを使ってレイアウトをやってみます。
まずは単純な配置から。

こんな感じのhtmlを用意します。
グリッドレイアウトを行うためのwrapperのdivを作り、その中にシンプルにdivを並べただけのものです。

  <div class="wrapper">
    <div class="box">A</div>
    <div class="box">B</div>
    <div class="box">C</div>
    <div class="box">D</div>
    <div class="box">E</div>
  </div>

gridレイアウトをする領域の定義

Gridレイアウトを行う領域に対して、display: gridというプロパティを指定します。

  • gridの分割数を定義
    gridの分割数は、grid-template-columns/grid-template-rowsという二つのプロパティで、Gridの分割数やサイズを指定します。
    ここでは、100px、150px、100pxという順番で3つの列と、高さが自動で計算される行を2つ定義します。

wrapperクラスの定義はこんな風になります。

    .wrapper {
      display: grid;
      grid-template-columns: 100px 150px 100px;
      grid-template-rows: auto auto;
    }

設定する単位について
グリッドのサイズは、pxやem、remなどの指定方法の他に、以下のような方法が用意されています。 * auto
auto指定をすると、auto指定をした行/列のサイズは、実際にその行/列に配置された要素のサイズに合わせて決定されます。 * fr
グリッドのサイズを、配置可能な余白のサイズから比率で指定します。
(↓のように○○frなどといった感じで指定します。)

grid-template-columns: 1fr 2fr;

こうすると、幅が1:2になるように、二つの列を定義できます。

グリッド上に自動で流し込む場合

先ほど定義した2行3列のグリッドの領域内に複数の要素を入れてみます。

    .wrapper {
      display: grid;
      grid-template-columns: 100px 150px 100px;
      grid-template-rows: auto auto;
    }

    .box {
      background-color: #444;
      color: #fff;
      border-radius: 5px;
      border: 2px solid black;
      padding: 20px;
      font-size: 150%;
    }
  <div class="wrapper">
    <div class="box">A</div>
    <div class="box">B</div>
    <div class="box">C</div>
    <div class="box">D</div>
    <div class="box">E</div>
  </div>

こんな風に定義したグリッドに順番にdiv要素が流し込まれていきます。
f:id:minami_SC:20151208234444p:plain:w300

流し込む方向の指定

grid-auto-flowというプロパティを使うことで、自動で要素をグリッドに流し込む際に、行/列どちらの方向に順に流し込むかを指定できます。

    .wrapper {
      display: grid;
      grid-template-columns: 100px 150px 100px;
      grid-template-rows: auto auto;
      grid-auto-flow: column; /*列方向に順に並べていくよう指定*/
    }

https://github.com/sourcechord/electron-gridlayout-sample/blob/master/view/flow_direction.html f:id:minami_SC:20151208234455p:plain:w300

グリッド上の特定の位置へ配置

今度は、htmlに定義した順に流し込むのではなく、cssでグリッド上の特定の位置を指定して配置してみます。 グリッド上の配置位置を指定するには、以下の4つのプロパティで、row/culumnの開始位置と終了位置を指定します。 * grid-row-start * grid-row-end * grid-column-start * grid-column-end

特定のセルに配置
    .a{
      grid-row-start: 1;
      grid-row-end: 2;
      grid-column-start: 2;
      grid-column-end: 3;
    }

f:id:minami_SC:20151208233513p:plain:w300

複数のセルにまたがった配置

こんな風に、複数のセルにまたがって配置することもできます。

    .b{
      grid-row-start: 2;
      grid-row-end: 3;
      grid-column-start: 2;
      grid-column-end: 4;
    }

f:id:minami_SC:20151208233519p:plain:w300

ショートハンド

gridの定義方法、配置方法ともに、簡潔に記述するためのショートハンドが用意されています。

  • グリッド定義のショートハンド
    f:id:minami_SC:20151208233435p:plain

  • 配置位置指定のショートハンド
    f:id:minami_SC:20151208233457p:plain

Z-Indexの指定

z-indexプロパティを指定することで、要素のが重なった際の順序指定ができます。
htmlに定義した順ではなく、このz-indexプロパティの値の大きい方を前面に表示することができます。

https://github.com/sourcechord/electron-gridlayout-sample/blob/master/view/z_index.html
f:id:minami_SC:20151208234558p:plain:w250

複雑なレイアウト

今度はもう少し複雑な例として、よくあるHolyGrail的なレイアウトを作ってみます。
htmlは↓みたいに、シンプルなものを用意しておきます。

  <div class="wrapper">
    <div class="box header">header</div>
    <div class="box left">left</div>
    <div class="box content">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati debitis nemo excepturi voluptatibus magnam fugit officia, ut, dicta, animi inventore quidem aliquam adipisci vitae perspiciatis id autem maiores asperiores dolores!</div>
    <div class="box right">right</div>
    <div class="box footer">footer</div>
  </div>

続いて、cssを書いていきます。
全体を、3×3のグリッドに分割します。

    .wrapper {
      display: grid;
      width: 100%;
      height: 100%;
      grid-template-columns: 150px 3fr 1fr;
      grid-template-rows: auto 1fr auto;
      padding: 10px;
    }

配置は、こんな感じ。
ヘッダーやフッター領域は複数のセルからなるエリアを指定して、こんな形にしています。

    .header{
      /*↓grid-areaで書く場合はこんな感じ*/
      /*grid-area: 1/1/2/4;*/
      grid-row: 1;
      grid-column: 1 / span 3;
    }
    .left{
      /*grid-area: 2/1/3/2;*/
      grid-row: 2;
      grid-column: 1;
    }
    .content{
      overflow: auto;
      /*grid-area: 2/2/3/3;*/
      grid-row: 2;
      grid-column: 2;
    }
    .right{
      /*grid-area: 2/3/3/4;*/
      grid-row: 2;
      grid-column: 3;
    }
    .footer{
      /*grid-area: 3/1/4/4;*/
      grid-row: 3;
      grid-column: 1 / span 3;
    }

f:id:minami_SC:20151208234704p:plain:w350
わかりやすいですね。

grid-template-areasを使って、AA風にレイアウトを定義

grid-template-areasプロパティを使うと、アスキーアートっぽく文字列でレイアウトの定義を行いつつ、エリアに名前を付けることができます。

先ほどのHolyGrail的なレイアウトの例を、grid-template-areasプロパティを使って書き直してみます。
grid-template-areasというプロパティを使い、グリッドの各セル/エリアに名前を付けていきます。
スペースで区切られた各文字列が一つのセルとなり、隣り合う同名のセル同士が一つのエリアとなります。

    .wrapper {
      display: grid;
      width: 100%;
      height: 100%;
      grid-template-columns: 150px 3fr 1fr;
      grid-template-rows: auto 1fr auto;
      grid-template-areas: 
          "header header header"
          "left  content right"
          "footer footer footer";
      padding: 10px;
    }

配置するときは、grid-areaプロパティにエリア名を指定するだけ。

    .header{
      grid-area: header;
    }
    .left{
      grid-area: left;
    }
    .content{
      overflow: auto;
      grid-area: content;
    }
    .right{
      grid-area: right;
    }
    .footer{
      grid-area: footer;
    }

レイアウト結果は同じなので、htmlのソースとキャプチャ画像は省略します。
こうしてみると、「ココの領域は開始位置がcolumn1で、spanは・・・」などと考えずに、直感的にレイアウトできると思います。
この発想はなかった。。。なるほどなぁ、って感じの仕様です。

メディアクエリを併用しレスポンシブなレイアウト

メディアクエリと組み合わせることで、レスポンシブなレイアウト切替も簡単に実現できます。 先ほどの例と同じレイアウトですが、ウィンドウサイズをリサイズして幅を550px以下にすると、縦に一列に並んだスマホ向けっぽいレイアウトになります。 f:id:minami_SC:20151208234757p:plain:w300

    .wrapper {
      display: grid;
      width: 100%;
      height: 100%;
      grid-template-columns: auto;
      grid-template-rows: auto;
      padding: 10px;
    }
    
    .box {
      background-color: #444;
      color: #fff;
      border-radius: 5px;
      border: 2px solid black;
      padding: 20px;
    }
    
    @media (min-width: 550px) {
      .wrapper {
        grid-template-columns: 200px 3fr 1fr;
        grid-template-rows: auto minmax(min-content, 1fr) auto;
        grid-template-areas: 
            "header header header"
            "left  content right"
            "footer footer footer";
      }
      .header{
        grid-area: header;
      }
      .left{
        grid-area: left;
      }
      .content{
        grid-area: content;
      }
      .right{
        grid-area: right;
      }
      .footer{
        grid-area: footer;
      }
    }

サンプル

今回の全サンプルをまとめたプロジェクトを↓に置いておきました。
sourcechord/electron-gridlayout-sample · GitHub
メニューから項目を選ぶことで、ページ表示が切り替わります。
また、メニューから選択 or 「Ctrl+Shift+I」のショートカットキーでDev Toolsが出てきます。
Dev Toolsの各種パネルで色々と動きを確認することができるかと思います。
f:id:minami_SC:20151208233530p:plain:w300

まとめ

以上、駆け足でしたが、CSS Grid Layout Module Level1を使ってElectronのアプリのレイアウトをしてみました。
実際に動かしてみると、こんなシンプルな記述で思い通りにレイアウトできる、CSS Grid Layout Moduleの威力を実感できるかと思います。

この機能をブラウザ上で気軽に使えるようになるのは、まだまだ遠い未来かもしれません。
しかし、Electronを使った開発であれば、そんな未来の世界を少し先取りして体験することができます。
あとは、この機能が気兼ねなくブラウザでも利用できるようになる日が早く来ることを願うばかりです。

明日はsoedaさんです。
よろしくお願いします♪