今回実現すること
フロントエンドをされている方なら1度は憧れる(?)
Pinterest風なGridレイアウトを
ライブラリを使わず、かんたんなJSで実現したので
過程と解説を書いていきたいと思います。
既に個人開発でつくったWebアプリには実装していて
問題なく動いてます。
もし実装する機会があれば、参考にしていただけれ幸いです。
まずは調査しました
今回、Pinterest風なGridレイアウトを作る上で、
foreachの順序を崩さずに、画面上から下へcreated_atの降順になるようにする
という仕様がありました。
順序を表すとこう言う感じです。
① ②
③ ④
⑤ ⑥
まず見つけた記事
【WordPress】cssの追記のみでPinterest風のグリッドデザイン
順序はこのようになってしまいます。
① ④
② ⑤
③ ⑥
次に見つけた記事
Masonryレイアウトをたった3行のシンプルなCSS Gridで簡単に実装できるようになります
めちゃくちゃ簡単そうですが
まだブラウザ互換的に使い物になりません。
ライブラリも発見
Pinterest風なGridレイアウトを簡単に作れるライブラリもあるようです。
driveway|http://jh3y.github.io/driveway/
Masonry|https://masonry.desandro.com/index.html
しかし
「たかだか1画面のレイアウトのためにライブラリいれるのもなあ…」
ということで自作することにしました
Pinterest風なGridレイアウトの原理
自作する前に、Pinterestが実際どのように実現しているかを調べてみました。
ソースコードを見るとおそらく
どうにかしてJSで各要素にstyleを書き込んでいるということが予想できます。
そこで、それを真似することにしました。
やることは、
要素のサイズや要素同士の余白計算して、
計算結果を次の要素のstyle transform
に設定する
ということだけです。
いたってシンプルですね。
コードと解説
簡易的にhtml
をこういうものだとします。(実際のWebアプリにはhtml
使ってませんが)
<div class="card-wrapper"> <!-- Gridレイアウトしたい親要素 -->
<div class="card"> <!-- Gridレイアウトしたい要素 -->
<img src="img01.png">
</div>
<div class="cardr">
<img src="img02.png">
</div>
<div class="card">
<img src="img03.png">
</div>
<div class="card">
<img src="img04.png">
</div>
<div class="card-">
<img src="img05.png">
</div>
<div class="card">
<img src="img06.png">
</div>
</div>
今回Pinterest風なGridレイアウトで表示したい要素には
画像が含まれています。
そこでまずは、画像が全て読み込まれたかどうかを判定します。
const images = document.querySelectorAll('img');
let loadCnt = 0;
if (images.length > 0) {
for (const img of images) {
let image = new Image();
image.src = img.src;
image.addEventListener('load', () => {
loadCnt++;
if (loadCnt == images.length) { // 画像が全部読み込まれたか判定しています
makePageCardGridLayout(); // この後、このメソッドの中身を書きます
}
})
}
}
ここまでで、ページないの画像全て読み込んだら、makePageCardGridLayout()
というメソッドが呼ばれるところまできました。
次にmakePageCardGridLayout()
でPinterest風なGridレイアウトを実現していきます。
makePageCardGridLayout() {
const cards = document.getElementsByClassName('card');
if(cards.length > 0) {
let evenPosX = 0;
let evenPosY = 0;
let oddPosY = 0;
let gridWindow;
let sh;
for (let i = 0; i < cards.length; i++) {
const oddPosX = cards[0].clientWidth;
i == 0 ? gridWindow = cards[0].closest('.card-wrapper') : i; // Gridの親要素を取得します
if (i % 2 == 0) { // Gridに配置される偶数番目の要素に対する処理
cards[i].setAttribute("style", "transform: translateX("+ evenPosX +"px) translateY(" + evenPosY + "px)"); // スタイルに配置したい位置を記述します
evenPosY = evenPosY + cards[i].clientHeight; // 今のY軸の位置に要素の高さを加算
} else if (i % 2 != 0) { // Gridに配置される奇数番目の要素に対する処理
cards[i].setAttribute("style", "transform: translateX(" + oddPosX + "px) translateY(" + oddPosY + "px)"); // スタイルに配置したい位置を記述します
oddPosY = oddPosY + cards[i].clientHeight; // 今のY軸の位置に要素の高さを加算
}
}
evenPosY > oddPosY ? sh = evenPosY + 150 : sh = oddPosY + 150; // 全部の要素が配置し終わったら、親要素の高さを設定するために計算します
gridWindow.style.height = sh+'px'; // 計算した高さを親要素に指定します
}
}
このように配置したい要素(.card
)を取得して、forで1つずつ配置する場所を指定してあげています。
(なんでfor
なのかは覚えていません。笑 for of
でもいけるはずです)
① ②
③ ④
⑤ ⑥
奇数番目の要素は、1つ前の奇数番目の要素の位置とサイズから配置位置を計算
偶数番目の要素も、1つ前の偶数番目の要素の位置とサイズから配置位置を計算しています。
transform: translate
で子要素の位置を指定しているので、
親要素の.card-wrapper
は高さが0の状態になっています。
そこで最後に、奇数番目か偶数番目で下側にある要素から、150px加算して親要素の高さに指定しています。
コードは以上です。
レスポンシブを実現するのも、
画面サイズによって、makePageCardGridLayout()
を流用したメソッドを呼び出せば
スムーズに実現できると思います。
結果、意外とかんたんにできた
「え、もうおわり?」
「思ったよりコード量少ないな」
と思われた方も多いんじゃないでしょうか。
実はこちらのWebアプリには、ライブラリを使用せずに
JSで実装した挙動が他にもいくつかあります。
チャットライクな寄書きWebサービス Cheers Box
たとえば、
- 寄書き作成時に、生のJSなのにSPAのようなプレビュー機能がある
- 寄書きは、メッセージがチャットのように1つずつ時間差で表示される
- 下記の画像下部の部分はメニューで、画面全体がメニュー連動のカルーセルになっている
それらもなるべくシンプルに実装してみたので
また実装方法を記事にできたらと思います。
より良い実装、おすすめのライブラリがあれば、ぜひご教授ください。
最後までご覧いただいてありがとうございました😊