サムネイルが右側に並んだ画像リストのCSSとJSをじっくりコーディング(CSS編)

大きな画像の横に、クリックして画像を切り替えるサムネイルが並んだレイアウトのCSS・JSコーディングの流れをじっくり解説していきます。前後編に分け、前編の本記事ではCSSについて解説します。

今回解説するものの完成形

先日、ウェブサイトをコーディングしていて、以下のような写真リストを実装する機会がありました。

成果物

横幅や余白などの仕様は以下のようなものでした。

仕様

これらの見た目や機能を実装する手順を、順を追って解説します。

HTML・CSSコーディング

大きな画像とサムネイルを表示する領域を作る

3

まずは、大きな画像が入る領域とサムネイルが入る領域を作ります。

2つを合わせた全体の幅は620pxとしました。大きな画像が入る領域(青い部分)は440pxとし、サムネイルの領域(赤い部分)は自動的にに決まる(この場合は180pxになる)、というようなコードを書いていきます。

大きな画像とサムネイルの間には10pxの余白が必要ですが、サムネイルの領域(赤い部分)の方に後ほど設定します。

html
<div class="imageList">
  <div class="imageList__view"></div>
  <div class="imageList__thumbs"></div>
</div>
css
.imageList {
  width: 620px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
  background-color: #66f; // 確認用のダミースタイル
}
.imageList__thumbs {
  flex: 1;
  background-color: #f66; // 確認用のダミースタイル
}

最上位の.imageListdisplay: flex;を指定して子要素を横並びにします。

子要素の.imageList__viewに指定したflex: 0 0 440px;は、flex-growflex-shrinkflex-basisの3つのプロパティの省略形(ショートハンド)です。flex: 0 0 440px;は、以下3行を書くのと同じ意味になります。

flex-grow: 0;
flex-shrink: 0;
flex-basis: 440px;

それぞれざっくり説明すると、

  • flex-grow: 0

    • 幅が余ってても、勝手に伸びない
  • flex-shrink: 0

    • 幅が足りなくても、勝手に縮まない
  • flex-basis: 440px

    • 440pxの横幅を確保しようとする(が、実際の横幅の値はflex-grow、flex-shrink、他のflexアイテム、親のflexコンテナーなどの条件によって決まる)

という感じです。(いずれこの部分の解説記事も作りたいと思います。)

一方、.imageList__thumbsに指定したflex: 1;flex-grow: 1の省略形(ショートハンド)で、ざっくり説明すると

  • flex-grow: 1

    • 幅が余っていたら伸びる

という感じで動きます。

この結果、全体の横幅の値を変えると、大きな画像が入る領域(青い部分)の横幅は変わらず、サムネイルの領域(赤い部分)の横幅だけが伸び縮みするようになります。

4

サムネイルを入れる領域を作っていく

領域を追加する

5

サムネイルを入れる領域のdivを9個追加します。写真の並ぶ順番が決めた通りになっているか確認しやすくするため奇数にしました。

サムネイルの領域が横一列に広くなっちゃっていますが、今の段階では気にせず進めます。

html
<div class="imageList">
  <div class="imageList__view"></div>
  <div class="imageList__thumbs">
    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>    <div class="imageList__thumbnail"></div>  </div>
</div>
css
.imageList {
  width: 620px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
  background-color: #66f;
}
.imageList__thumbs {
  flex: 1;
  display: flex;  background-color: #f66; // 確認用のダミースタイル
}
.imageList__thumbnail {  width: 80px; // 確認用のダミースタイル  height: 80px; // 確認用のダミースタイル  background-color: #6f6; // 確認用のダミースタイル}.imageList__thumbnail:nth-child(2n) {  background-color: #f6f; // 確認用のダミースタイル}

追加した9個の.imageList__thumbnailを横並びにするため、親の.imageList__thumbsdisplay: flexを指定します。(これにより.imageList__thumbsは親の.imageListのフレックスアイテムでありながら、フレックスコンテナーとしても振舞うことになります。)

imageList__thumbnailのスタイルは必要ありませんが、何も指定しないと透明のままなので、とりあえず確認用のダミースタイルを当てておきます。

追加した要素が領域内で折り返されるようにする

6

css
.imageList {
  width: 620px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
  background-color: #66f;
}
.imageList__thumbs {
  flex: 1;
  display: flex;
  flex-wrap: wrap;  background-color: #f66; // 確認用のダミースタイル
}
.imageList__thumbnail {
  width: 80px; // 確認用のダミースタイル
  height: 80px; // 確認用のダミースタイル
  background-color: #6f6; // 確認用のダミースタイル
}
.imageList__thumbnail:nth-child(2n) {
  background-color: #f6f; // 確認用のダミースタイル
}

.imageList__thumbsflex-wrap: wrap;を指定することで、サムネイルの領域が折り返されるようになりました。

しかし、まだ位置や余白がおかしいので、調整していきましょう。

サムネイルに余白を設定する

画像と画像の余白は10pxとることにしました。方法はいろいろ考えられますが、今回は

  • それぞれのサムネイル画像領域の左側に10pxのマージンを持つ
  • 3つ目以降のサムネイル画像領域の上側に10pxのマージンを持つ

としてみます。

7

css
.imageList {
  width: 620px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
  background-color: #66f; // 確認用のダミースタイル
}
.imageList__thumbs {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  background-color: #f66; // 確認用のダミースタイル
}
.imageList__thumbnail {
  margin-left: 10px;  width: 80px; // 確認用のダミースタイル
  height: 80px; // 確認用のダミースタイル
  background-color: #6f6; // 確認用のダミースタイル
}
.imageList__thumbnail:nth-child(n + 3) {  margin-top: 10px;}.imageList__thumbnail:nth-child(2n) {
  background-color: #ff6; // 確認用のダミースタイル
}

完成形に近いレイアウトが見えてきました。

ただし、この段階では確認用のダミースタイルのおかげでそれっぽく見えているだけで、実際に画像を入れてみるとレイアウトが崩れます。

ここからは、領域に画像を追加してコードを描いていきましょう。

領域に画像を追加する

簡単にダミー画像が利用できるLorem Picsumというサービスを利用して、それぞれの領域に画像を追加します。

8

html
<div class="imageList">
  <div class="imageList__view">
    <img src="https://picsum.photos/id/22/400/400" />  </div>
  <div class="imageList__thumbs">
    <div class="imageList__thumbnail selected">
      <img src="https://picsum.photos/id/22/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/33/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/44/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/55/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/66/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/77/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/88/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/99/400/400" />    </div>
    <div class="imageList__thumbnail">
      <img src="https://picsum.photos/id/1010/400/400" />    </div>
  </div>
</div>
css
.imageList {
  width: 620px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
}
.imageList__thumbs {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  background-color: #f66; // 確認用のダミースタイル
}
.imageList__thumbnail {
  margin-left: 10px;
  width: 80px; // 確認用のダミースタイル
  height: 80px; // 確認用のダミースタイル
}
.imageList img {  width: 100%;  display: block;}

画像サイズはすべて幅400px・高さ400pxとしました。あえて領域にぴったり合わない値を使っているので、領域に合うようwidth: 100%も追加します。

display: blockは、画像がインライン要素であるために出てしまう意図しない下余白の対策で使用しています。

確認用のダミースタイルのうち、不要になった背景色指定は削除しました。

一見問題ないようにも見えますが、サムネイルの横幅を80pxと固定してしまっているので、一番外側の要素の横幅を大きくすると、変化に対応できず以下のようになってしまいます。

9

サムネイルの横幅の指定を可変にする

上記の見た目崩れの原因の、サムネイルの横幅width: 80pxの部分を修正します。

10

css
.imageList {
  width: 820px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
}
.imageList__thumbs {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  background-color: #f66;
}
.imageList__thumbnail {
  margin-left: 10px;
  flex: 0 1 calc(50% - 10px);}
.imageList__thumbnail:nth-child(n + 3) {
  margin-top: 10px;
}
.imageList img {
  width: 100%;
  display: block;
}

.imageList__thumbnailに指定されていたwidth: 80px;flex: 0 1 calc(50% - 10px);に変更しました。

これは、

flex-grow: 0;
flex-shrink: 1;
flex-basis: calc(50% - 10px);

の省略形(ショートハンド)になっていて、それぞれざっくり説明すると、

  • flex-grow: 0

    • 幅が余ってても、勝手に伸びない。
    • ここを0にしないと、奇数個の場合の最後のサムネイルの画像が大きくなってしまう
  • flex-shrink: 1

    • 幅が足りなくなったら、均等に縮む
  • flex-basis: calc(50% - 10px)

    • 幅の半分から、margin-left: 10pxの分だけ引いた値の横幅を確保する。

といった感じです。

height: 80px;も削除しているため、画像の高さは縦横比から成り行きで決まります。

はみ出た部分をスクロールさせて隠す

11

css
.imageList {
  width: 820px;
  display: flex;
}
.imageList__view {
  flex: 0 0 440px;
  height: 440px;
}
.imageList__thumbs {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  height: 440px;  overflow: scroll;}
.imageList__thumbnail {
  margin-left: 10px;
  flex: 0 1 calc(50% - 10px);
}
.imageList__thumbnail:nth-child(n + 3) {
  margin-top: 10px;
}
.imageList img {
  width: 100%;
  display: block;
}

高さに、大きな画像が入る領域の高さと同じ440pxを設定し、超えたはoverflow: scroll;で隠しました。

これで、全体の横幅が変化してもサムネイル画像の領域がいい感じに変化してくれるようになりました。全体の横幅を当初の620pxに戻すと

12

ぴったり収まります。(ついでに背景色も追加しました。)

個人的には、サムネイル部分がぴったりサイズではなく少し欠けている方が、スクロールできることが伝わりやすくて良いと感じますが、この辺りはデザインの要件次第という感じです。

終わりに

これで、CSS部分は完成です。最終的なコードをご覧になりたい方は、以下のCodePenにアクセスしてください。

完成形のソースコードを見る

CodePenでは、HTMLのプリプロセッサーにPug、CSSのプリプロセッサーにSCSSを使用しています。PugやSCSSの記法に馴染みのない方は、PugHtmlSassMeisterなどのサービスにコードをコピペしてHTMLやCSSをご確認頂ければと思います。

次回は、サムネイルをクリックで大きい画像を切り替える機能のJavaScriptについて解説していく予定です。

最後までお読みいただきありがとうございました。