CSSのflexで画像をタイル状に中央寄せで並べつつ最後の行だけ左寄せにしたい
フラットなデザインが好まれる昨今、写真やコンテンツを四角いタイル状に並べることって多いと思うんです。
CSSも便利になり、floatであれこれやらずとも display:flex; だけでレスポンシブな配置をしてくれるし、justify-content:center; で良い感じに中央寄せまでしてくれる。
しかし……しかしですよ。
レスポンシブな中央寄せは最後の行だけ気持ち悪い
表示例
justify-content:center;で写真をタイル状に並べた例ですが、こんなふうになっているページってありませんか?
横列の個数が合わない最後の行だけ浮いている…。
HTMLソース
<div style="display:flex;flex-wrap:wrap;justify-content:center;border:1px solid blue;width:100%;">
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
</div>
左寄せでflexboxを使うと余白が気持ち悪い
表示例
中央寄せだと最後の行の個数が合わないときに変になるからといって、justify-content:left;にすると今度は右の余白が気になります。
HTMLソースはjustify-contentをleftにしただけなので省略します。
CSSだけではどうしようもない
2番目の例の右余白を削って、中央寄せにするだけなんだから、あと一手間加えたら綺麗に整えられそうに感じますよね。
でも、実際CSSだけでは画像をタイル状に並べつつ、画面サイズによって折り返された最後の行だけは左寄せにする、というのは難しい。
もちろん、必ず2カラムや3カラムにする、とか、画像のサイズも変えて良い、とか、はたまたメディアクエリーで想定される画面サイズ分の分岐を書くとかアホらしいことをするなら出来ますけど、メンテナンス性が悪すぎる。
冷静に考えれば当たり前なのですが、先述の例では画像が折り返すか折り返さないかを親要素(div)の長さで判断しているわけで、折り返した後に親要素のサイズを変更することはできないし、その逆に親要素の幅を無くそうものなら、折返し自体ができなくなるっていう。
つまり、画像をタイル状に並べる場合、「親要素の横幅 ÷ 画像の幅」を算出して、切り捨てた値を親要素の幅にしなければなりません。
うん、何言ってんだって話ですよねw
例えば親要素(div)の幅が700pxとします。その中に入る画像の幅が160px。このとき、画像は横に何枚並べられる?っていう話。
700px÷160px=4.375 なので、最大で4枚。
ということは160px×4枚=640pxなので、60px分の右余白が出来てしまう、ということ。
JavaScriptで親要素の横幅を変更してあげれば良いじゃない
表示例
これよ、これ! 画面の中央に寄せつつ、最後の行は左寄せです。
HTMLソース
<div id="div_photo" style="display:flex;flex-wrap:wrap;justify-content:left;margin:0 auto;border:1px solid blue;width:100%;">
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
<img src="[画像]" width="160" />
</div>
<script>
let img_width = 160;
let w = Math.floor(document.getElementById('div_photo').clientWidth / img_width) * img_width;
document.getElementById('div_photo').style.width = w + 'px';
</script>
解説
- div要素にid="div_photo"を追加
- div要素にmargin:0 auto;を追加
- JavaScriptで親要素(div_photo)の幅を取得し、画像の幅から最適な幅を計算
- 計算結果を親要素(div_photo)の幅に設定
これだけです。
divのmargin:0 auto;はdiv要素全体を中央寄せにするおまじない。
img_width = 160;というように画像の横幅をハードコーディングしてしまっていますが、実際に使うときは画像にもIDふって横幅を取得するようにすれば良いかと思います。
先述したとおり、切り捨て計算が必要なだけですので、CSSのcalcで切り捨て計算が出来ればなぁ。
<style>
.div_photo {
width:calc(切捨(100% / 160) * 160);
}
</style>
こんなふうに出来ると良いのですが、出来ないものは仕方がない。
ウィンドウのリサイズにも対応する場合
表示例
スマホサイトの場合は気にしなくても良いと思いますが、今どきはPCとスマホの両対応するのが普通でしょう。
このブログはPCだと記事部分が固定幅になってしまうので、無理やりページの幅を元にして計算するようにしてみました。ブラウザの大きさを変えるとタイルの並ぶ数が変わりつつも、常に中央に配置されるのが確認できるかと思います。
HTMLソース
<script>
window.addEventListener('resize', function () {
let img_width = 160;
let w = document.body.clientWidth;
w = Math.floor(w / img_width) * img_width;
document.getElementById('div_photo').style.width = w + 'px';
});
</script>
解説
タグ部分に変更はないので、JavaScript部分のみ掲載しました。
window.addEventListenerで、ウィンドウのonresizeイベント時に幅の計算を行うようにしています。ウィンドウではなく、div等の要素のonresizeイベントがあるともっと便利なんですけどねー。ないようなのでwindowで代用。
その中ではdocument.body.clientWidth;でボディの横幅を取得してそこを基準にして幅を調整していますが、実際使うときはdiv_photoの親要素なら何でも良いです。
コンテンツとサイドメニューで2カラムにしている場合もあるでしょうし。
あと、このままではresize時にしか調整してくれないので関数化してdocument.onload時にも呼ぶようにすれば使い勝手も良くなるのではないでしょうか。
まとめ
- CSSのflexboxのjustify-content:center;は便利だが、最後の行だけ左寄せにするのは難しい。
- かといってタイルを全部左寄せにすると右側の余白がダサい。
- カラム数が固定ならまだしも、レスポンシブで綺麗にタイルを並べるにはCSSだけでは難しそうなのでJavaScriptで幅を調整してみた。
といったところでしょうか。
これが必ずしも正解というわけではありません。
ちょっとググればわかると思いますが、同じようにJavaScriptを使っていても、足りないタイルを追加(appendChild)することで対応している例もあります。
ただ、それだと無駄な要素が増えることになり、JavaScriptで要素を走査する際に問題が発生しそうだったのでぼくは幅を調整する方向で対応してみました。
この記事がどこかの誰かのお役に立てば幸いです。