思ってることってこんなもんだよ
このブログをレスポンシブにした話

今年の元旦にリニューアルしたこのブログですが、Nexus6で見たら、以下のように非常に残念なことになってました。

10583971_10153836236394539_4105311014366597681_n.jpg

Polymerを使ってれば勝手にレスポンシブ対応されると思っていたのですが、そんなに世の中甘くなかったです。というか、Polymerが期待する書き方をしていれば勝手にレスポンシブになったんですけど、今回の僕はそう書いてはいかなったので、上記のような結果になってしまいました。何とかしなければなりませんが、何とかしました。

ここでは、モバイル端末のWebブラウザで見ても大丈夫なようにするために何をしたのか、そもそも何がダメだったのか、紹介してみたいと思います。

どうしたかったのか?

まず、どのようなレイアウトにしたかったのかを説明しておきましょう。いまこのブログをPCで見ている場合は、ブラウザのウィンドウサイズの横幅を縮めていただければわかると思います。具体的には、ブラウザのウィンドウサイズが十分に広い場合(PCで見ている状態)は、以下のような3つの区画で配置します。

responsive_1.png

そして、横幅が狭い場合(スマートフォンで見ている状態)は、以下のように3つの区画が縦に並ぶように配置したいと考えました。

responsive_2.png

ブラウザのクライアント領域の横幅に応じて、サイドバーの位置を右と下に自動的に切り替わる、という感じですね。普通のレスポンシブだと思います。ここでのポイントは、「ヘッダ領域は”1つ”で、必ず”画面上部に固定されている”」ということです。完全に僕の主観ですが、ここは「絶対に譲れない仕様」です。そして、ヘッダ領域はPolymerの_paper-header-panelのwaterfall-tallモードを使って、縦に伸縮するUIとする、というのも外せません。

waterfall-tallモードは、以下のページで見ることができますが、つまりはこのブログの上部の「にょき、にょき」がそうです。

paper-header-panel#waterfall-mode

レスポンシブ未対応だった最初の構成

このブログをリニューアルしたいなと思ってた動機の一つとして、Polymerを使いたかった、というのがあります。リニューアル当初の構成は、メイン領域とサイドバー領域をPolymerのレイアウト機構を使って左右に並べる、という単純なものでした。

<paper-header-panel mode="waterfall-tall">

  <div class="paper-header">
    <!-- ブログ名称とか -->
  </div>

  <div class="layout horizontal">
    <div class="flex main">
      <!-- メイン領域 -->
    </div>
    <div class="sidebar">
      <!-- サイドバー領域 -->
    </div>
  </div>

</paper-header-panel>

まあ単純ですね。これで「レスポンシブだぜ」と思った自分は一体何を血迷っていたのでしょうか。Polymerを使いたかった最大の理由「にょきにょきヘッダ領域」も無事実現し、メイン領域とサイドバー領域も思っていたとおりの配置になりました。PCでの確認だけですけど。

ここでのポイントは、メイン領域のdiv要素にflexが付いていることですね。そして、.sidebarのスタイル定義で”width: 310px;”と固定幅を指定していた(リキッドではなかった)こともあり、結果として最初の画像のように残念な結果になりました。つまり、メイン領域が横幅を十分に使うことができる代わりに、ブラウザの横幅を狭めれば当然メイン領域だけが狭まっていく、という副作用(?)も出てしまった、ということです。

10583971_10153836236394539_4105311014366597681_n.jpg

この現象に気がついた時に初めて、「あ、真面目にレスポンシブ対応せにゃあかん」と思いました。

paper-drawer-panel in paper-header-panelは大失敗

Polymerのlayout_ _horizontal指定がレスポンシブではない、と知った後、ではPolymerでレスポンシブ対応するにはどうしたら良いのかを学ばなければなりません。というか、最初から気がついていたのですが、paper-drawer-panelというコンポーネントを使うことで、ブラウザの横幅に応じて「横に並べるか」「縦に並べるか」を切り替えてくれます。以下のデモを見ると良いでしょう。

paper-drawer-panel

上記のページをPCのWebブラウザで表示した状態で、そのブラウザの横幅を小さくしてみてください。drawerをつけたdiv要素が非表示になり、左上のハンバーガーボタン押すことで、左からにょきっと出てくる動作になることがわかります。

今回のブログではサイドバー領域を右に置きたいので、right-drawerを付与すれば良さそうです。概念図は以下のような感じですね。

responsive_3.png

当初の僕の想定は、ブラウザの横幅が狭い場合は自動的に以下のようになると思ってました。まあ、隠れるか、下に来るか、その差だけなので、どちらもレスポンシブ対応としては成り立ってると思います。

responsive_4.png

paper-header-panelを使う都合上、メイン領域とサイドバー領域をその中に収めないといけないので、panel-drawer-panelがその中に収まります。タグ構成は以下のようになります。

<paper-header-panel mode="waterfall-tall">

  <div class="paper-header">
    <!-- ブログ名称とか -->
  </div>

  <paper-drawer-panel right-drawer>
    <div main>
      <!-- メイン領域 -->
    </div>
    <div drawer>
      <!-- サイドバー領域 -->
    </div>
  </paper-drawer-panel>

</paper-header-panel>

「これで完璧だぜ!」と思って動作確認しましたが、見事に機能しませんでした。メイン領域とサイドバー領域はちゃんと左右に描画されたのですが、paper-header-panelが全く機能しなくなりました。縦に伸縮しなくなったどころか、ページがスクロールできなくなってしまいました。試しにpaper-drawer-panelをdiv要素で囲ってみたりしてみましたが、何の効果もありませんでした。

responsive_5.png

paper-header-panelの機能不全の原因を調査したかったのですが、軽くネット検索しても特に何もヒットせず、気になることとしては、PolymerのPaper Element Catalogのpaper-drawer-panelDocsを見ると、paper-drawer-panelの中でpaper-header-panelを使っている例が出ていました。以下のコードが、Docsページに掲載されていた例です。

<paper-drawer-panel>
  <paper-header-panel drawer>
    <paper-toolbar></paper-toolbar>
    <div> Drawer content... </div>
  </paper-header-panel>
  <paper-header-panel main>
    <paper-toolbar></paper-toolbar>
    <div> Main content... </div>
  </paper-header-panel>
</paper-drawer-panel>

Polymerが適用されているようなWebサイトをいくつか見てみましたが、サイドバー領域を持っていて、ヘッダ領域がブラウザの横幅いっぱいに配置されているようなWebサイトを見つけることができませんでした。

ここで、嫌な予感がしました。実は「paper-header-panelの中にpaper-drawer-panelを入れて使うことは想定外なのではないか・・・」ということです。たぶん、この予想は当たってます。

paper-drawer-panelがそもそも使えなかった問題

「わかったよ、そこまで言うなら、Polymerの言う通りにするよ!」という勢いで、絶対に譲れないと考えてたpaper-header-panelの上部幅100%固定化を諦めて、paper-header-panelpaper-drawer-panelの親子関係を入れ替えることにしました。ブログのレイアウトが以下のようになります。

responsive_6.png

タグ構成は以下のように変更してみました。

<paper-drawer-panel right-drawer>

  <div main>

    <paper-header-panel mode="waterfall-tall">
      <div class="paper-header">
        <!-- ブログ名称とか -->
      </div>
      <!-- メイン領域 -->
    </paper-header-panel>

  </div>

  <div drawer>
    <!-- サイドバー領域 -->
  </div>

</paper-drawer-panel>

この結果、特に1つの記事を表示するページが、以下のようにひどい壊れ方をしてしまいました。

responsive_7.png

なんでこんな風な壊れ方をするのか、それこそ原因が掴めませんでした。気になることとすれば、記事一覧ページと比べてpaper-toolbarを使っている程度なのですが、そんなことでここまでの壊れ方をするもんなのかと途方に暮れました。また、サイドバー領域に貼ったFacebookやTwitterなどのガジェットがiframeなのですが、それらを消すと表示が少しまともになったりはしました。しかし、少なくともPC向けにはこれらのガジェットは貼っておきたいので、消すことは解決策になりません。

Polymerの正攻法を使っても上記のような感じでダメで、もしかしたらもう少し頑張れば解決できたかもしれないけど、元々の大前提「paper-header-panelを画面上部に幅100%で固定させる」を捨ててるわけで、それでもダメだったので萎えました。

結局自前でレスポンシブ対応

「もうPolymerやめてBootstrapに浮気しようかな」とも思いましたが、浮気は良いことではありません。「Polymer使うんだ!paper-header-panel使うんだ!レスポンシブにするんだ!」という初心を大事にすべきです。コードを元に戻して、最初からやり直すことにしました。

まず、当初書いたコードを再度見直してみます。

<paper-header-panel mode="waterfall-tall">

  <div class="paper-header">
    <!-- ブログ名称とか -->
  </div>

  <div class="layout horizontal">
    <div class="flex main">
      <!-- メイン領域 -->
    </div>
    <div class="sidebar">
      <!-- サイドバー領域 -->
    </div>
  </div>

</paper-header-panel>

上記の場合、メイン領域とサイドバー領域を横に並べたいので、div要素にlayouthorizontalを指定しました。では、ブラウザの横幅が狭い時にメイン領域とサイドバー領域を縦に並べたいときはどうしたら良いか?それは、horizontalではなくverticalを指定すれば良いわけです。

...
  <div class="layout vertial">
    <div class="flex main">
      <!-- メイン領域 -->
    </div>
    <div class="sidebar">
      <!-- サイドバー領域 -->
    </div>
  </div>
...

「よし、ブラウザの横幅に応じて、div要素のclass属性値を変化させれば良いのか!」と思いましたが、それだとJavaScriptを書くことになってしまいそうなので、それはやりたくありません。CSSレベルで解決したい。そのためには、layouthorizontalverticalがスタイル定義にどう反映されているかを知る必要があります。

調べたところ、以下のような感じでした。

  • layout: “display: flex;”
  • horizontal: “flex-direction: row;”
  • vertical: “flex-direction: column;”

つまり、CSSメディアクエリを使って、flex-directionの値を切り替えるようにすれば、レスポンシブ対応ができそうです。

まず、HTMLを以下のように書き換えました。layouthorizontalの指定ではなく、新規にcontainerというクラスを指定しています。

...
  <div class="container">
    <div class="flex main">
      <!-- メイン領域 -->
    </div>
    <div class="sidebar">
      <!-- サイドバー領域 -->
    </div>
  </div>
...

そして、cssファイルに以下の記載を行いました。

.container {
  display: flex;
}

@media only screen and (min-width: 769px) {
    .container {
        flex-direction: row;
    }
    .sidebar {
        width: 310px;
    }
}

@media only screen and (max-width: 768px) {
    .container {
        flex-direction: column;
    }
    .sidebar {
        width: 100%;
    }
}

これによって、ブラウザの横幅が768ピクセル以下であれば、column値が適用されてメイン領域とサイドバー領域が縦に並ぶようになりました。ついでに、サイドバー領域の横幅も固定値ではなく横幅いっぱいになるようにしています。

responsive_8.png

実際のこのブログでは、スマホ向けにはFacebookやTwitterのガジェットを非表示にしたり、シンタックスハイライトされるコードの文字の大きさやmarginを変化させたりしています。Polymerのpaper-drawer-panelを使った場合にそれらをどう変化させれば良いかわかりませんが、上記のように自前で行ってしまったおかげで自由が効くようになりました。

まとめ

今後layouthorizontalのスタイル定義がPolymerのバージョンアップによって変更された場合が心配ですが、とりあえず上記の対応によって、当初行いたかったことはできるようになりました。Polymerのセオリー(つまりMaterial Designのポリシー)には反してしまったかもしれませんが、もしpaper-header-panelを上部に幅いっぱいに固定したままメインコンテンツをレスポンシブ対応したくなった際には、ここで述べた小手先テクニックをぜひお試しください。