レスポンシブで要素数・高さ可変のボックスをタイル状に揃えて並べる
はじめに
はじめまして、エンジニアの安部です。
実案件で、RWD(レスポンシブ・ウェブ・デザイン)で要素数・高さ可変のボックスをタイル上に並べ、画面幅に合わせて列数を変える(かつIE8対応)ということをやったので紹介します。
背景
CMSやテンプレートエンジンを用いて、カタログや写真の要素を複数行・複数列に並べることがあるかと思います。
要素数が可変でRWDの場合、段ごとに親要素でくくることができないため高さが違う要素でレイアウトが崩れてしまいます。
これをflexboxなどは使わずゴリゴリ手で書いて対応してみます。
実装
使うもの
- HTML: data属性
- CSS: float
- JavaScript: jQuery
- JavaScript: jQuery.heightLine
- JavaScript: underscore.js
マークアップ
繰り返しの基本的な構造は以下のようになります。
(省力化のため、jadeで記述しています。jadeについてはこちら)
商品が増えるとli要素が繰り返されるイメージですね。
「売り切れになっている商品はカートに入れるボタンを表示させない」という仕様の想定です。
data属性でli要素に連番をつけます。これはJavaScript側で使います。
ループのインデックスや、ループの外側で定義した値をループの終わりにインクリメントして、data-index属性に連番をあてておきます。
スタイリング
floatでli要素を流し込みます。今回は画像はどんなサイズでも一定の範囲内でスケールさせる想定です。
通常の画面幅では3列に並べるとして、SCSSを書いてみます。
変数を多く使っているのでわかりにくいかもしれませんが、単純にclearfixをかけてfloatにしているだけです。
メディアクエリで画面幅が小さくなった際はli要素の幅のパーセンテージを変更する記述を書いておきましょう。
スクリプト
はじめに
まず、HTMLでjQuery, underscore.js, jQuery.heightLine.jsを先に読み込んでおきましょう。
heightLine.jsはjQueryに依存していますので、jQueryの後に読み込む必要があります。
ユーザの操作フローに沿って実装を考える
では実際のJavaScriptの実装を考えます。
- 初期ロード時点で、メディアクエリのブレークポイントによって変化する列の数から段を判定し、それに合わせて段ごとに高さを揃える
- リサイズされてブレークポイントを超えたら、すでに揃えてある高さをリセットし、段を判定し、高さを揃える
といった感じでしょうか。
操作フロー上の処理を詰める
必要な操作を詰めていきましょう。
1. 初期ロード
今回は画像のロードがされる前でも後でも要素が一定の高さになっているため、window.onload
ではなくDOMContentReady
に合わせるので問題ありません。
2. メディアクエリのブレークポイント判定
ブレークポイント前後でdisplayが変更される要素があればそちらのCSSのdisplayを取得すればCSS側の変更に対応できます。
しかし、簡略化で今回はJavaScript側で定数でもつこととします。
もしマジックナンバーをそれぞれ管理するのが大変なようであればJSONでconfig値を持ち、gulp-dataやgulp-json-sassを使って依存解決をするのがいいですね(※そちらのほうがたいてい面倒です)。
$( window ).width()
で画面幅を取得し、それがブレークポイント以上か以下かで状態を制御します。
3. 要素がどこに配置されるか判定する
要素がどの段にいるかを判定するには、data-indexでつけた連番と列数の商を切り捨て/切り上げで判定できます。
(ex. 4列の場合、15番目の要素は 15 / 4 = 3.75 で、4段目になります。jsの配列の添字的には3になるので小数点の扱いは好き好きですね)
列数ですが、ブレークポイントと同様こちらも一旦ハードコーディングしてしまいます。依存解決が必要ならプリプロセスを工夫しましょう。
1.で取得した画面幅に応じて列数を切り替えます。
4. 段数ごとに要素をグルーピングする
段数を判定する方法はわかりましたが、これでli要素をグルーピングしなければいけません。
jQuery.heightLineは複数要素のjQueryオブジェクトを一度にheightLineする必要がありますので$.each
などでは不適となります。
ここでunderscore.jsの出番です。段ごとにグルーピングされた配列もしくはオブジェクトが欲しいので、ここではgroupByメソッドが最適です。
イテレータと呼ばれるループ処理に渡す関数で、3.で示した処理を使えば段ごとにグルーピングされたオブジェクトが返されます。
5. リサイズイベントでの画面描画の負荷を軽減する
リサイズ後の処理では、resizeイベント後に毎度毎度heightLineが走ってしまうのは画面描画のボトルネックになりそうです。
そのため、列数が変わるときだけ処理を行うべきです。
初期ロードで取得された列数を保持しておき、resizeイベントで画面幅に合わせた中間変数で列数がどうなっているかを取得し、比較することで処理をreturnできますね。
出来上がったものがこちらです
以上の5点を考えながら実装してみると、こんな感じです。
グルーピングとheightLineの処理は共通化が出来そうですね(GitHubリポジトリの方では共通化します…)。
完成
これが反映されて完成されたものはこちらです。
リサイズしても崩れていませんよね?(崩れていたらこっそりコメントをください)
さいごに
長くなりましたが、いかがでしょうか。
最初に示した例はビルドツールも含めてgithubでリポジトリに上げてありますので、よければ御覧ください。
tipsnote/underscore-heightline