2019.01.14

入門書の次にチャレンジ!JavaScript実践クイズ〜画像ギャラリー編〜


この記事では、入門書などを読んで JavaScript の文法をひととおり知ったあとに、より実践的に JavaScript の活用法を学べるクイズを紹介します。応用が効くように、実際の開発で「ありがち」な実装パターンを扱っていきたいと思います。

問題

画像ギャラリーを作ってみよう

今回の問題では画像ギャラリーを実装します。

See the Pen JavaScript実践クイズ〜画像ギャラリー編〜 by Masahiro Harada (@MasahiroHarada) on CodePen.

後述する仕様の通りに動作するように JavaScript を編集しましょう。
穴埋めになっているので /* Insert code here... */ の箇所にコードを足してください。

HTML と CSS は出来ているので変更しません。
ただし重要なヒントですのでよく確認してください。

手元にソースコードを落としてきたい場合は GitHubGist からどうぞ 🐙

前提条件

  • jQuery などのライブラリは使用せずに実装してください。
  • 対応ブラウザは Google Chrome 最新版とします。
  • 解答例では ES2015 以降の文法も使用します。

画像ギャラリーの仕様

サムネイル(小さい画像)をクリックすると、メイン画像(大きい画像が)切り替わります。

initGallery 関数でギャラリーを初期化します。

const elem = document.getElementById('myGallery');
initGallery(elem);

data-gallery-image 属性が付与されている要素をクリックすると、その属性の値を id 属性に持つ要素が表示されます。

<div id="myGallery">
  <div id="myImage"><img src="..." alt="" /></div>
  <div data-gallery-image="myImage"><img src="..." alt="" /></div>
</div>

🤔

🤔

🤔

回答例

/**
 * ギャラリーを初期化する
 *
 * @param {HTMLElement} root ギャラリー全体のルート要素
 */
function initGallery (root) {
  // サムネイル画像
  const thumbnails = root.querySelectorAll('[data-gallery-image]');

  // メイン画像のIDをサムネイルから取得
  const targetIds = Array.prototype.map.call(thumbnails, tn => {
    return "#" + tn.getAttribute('data-gallery-image');
  });

  // メイン画像
  const mainImages = root.querySelectorAll(targetIds.join(','));

  // サムネイルのクリックイベントハンドラ
  function onClickThumbnail () {
    // まずメイン画像をすべて隠す
    mainImages.forEach(image => image.classList.remove('active'));
    // 表示するメイン画像のIDを取得する
    const id = this.getAttribute('data-gallery-image');
    // 表示するメイン画像を取得する
    const target = document.getElementById(id);
    // 要素があったら表示する
    if (target) {
      target.classList.add('active');
    }
  }
  
  thumbnails.forEach(tn => {
    tn.addEventListener('click', onClickThumbnail);
  });
}

const elem = document.getElementById('myGallery');
initGallery(elem);

解説

今回のポイントは data 属性でしょう。

✏ ✏ ✏

まず解くべき問題は、どうやってメイン画像の表示と非表示を切り替えているのか?です。
前回のモーダル編同様、CSS を確認していただくとこの問題は解けます。

.gallery-image-item {
  display: none;
}

.gallery-image-item.active {
  display: block;
}

active クラスの有り無しで表示 / 非表示を切り替えるようです。

HTML を見て、メイン画像のひとつ目だけに最初から active クラスが付けられていることからも予想ができるかもしれませんね。

✏ ✏ ✏

メイン画像を表示したり隠したりする方法が分かったので、あとはどのようなときに active クラスを付けて、逆にいつ外すのかを考えればよいことになります。

では initGallery 関数から確認していきましょう。
冒頭のサムネイル要素の取得は分かるとして、その次ではメイン画像要素を取得しています。

const targetIds = Array.prototype.map.call(thumbnails, tn => {
  return "#" + tn.getAttribute('data-gallery-image');
});
// -> ["#img1", "#img2", "#img3"]

サムネイルをクリックしたらメイン画像が表示されるというのですから、サムネイルの data-gallery-image 属性の値を集めてきて、その値を id 属性に持つ要素を取得すればそれがメイン画像であるはず、という発想です。

targetIds.join(',')
// -> "#img1,#img2,#img3"

結局、以下のコードが実行されることになります。

const mainImages = root.querySelectorAll("#img1,#img2,#img3");

ここまででメイン画像要素を取得できました。
続いて onClickThumbnail 関数の空欄部分を見ていきます。

まずメイン画像をすべて隠すと書かれています。

mainImages.forEach(image => /* Insert code here... */);

すべて隠してから条件に合うもののみ再表示するというロジックです。
メイン画像を隠すには active クラスを外せば OK です。

mainImages.forEach(image => image.classList.remove('active'));

別の実装案としては、以下のように active クラスがついているメイン画像のみから active クラスを外すというのもあり得るでしょう。

mainImages.querySelector('active').classList.remove('active');

次の空欄は「表示するメイン画像のIDを取得する」というものです。

const id = /* Insert code here... */;

仕様通り、クリックされたサムネイル要素の data-gallery-image 属性の値を取得します。

const id = this.getAttribute('data-gallery-image');

id が取得できたあとは「表示するメイン画像を取得」します。

const target = /* Insert code here... */;

ここはシンプルに getElementById です。

const target = document.getElementById(id);

表示すべきものが分かればあとは簡単でしょう。

// 要素があったら表示する
if (target) {
  target.classList.add('active');
}

✏ ✏ ✏

さて空欄を埋められたところで、今回のポイント「data 属性」について説明します。

data 属性(カスタムデータ属性とも呼ばれる)とは、ざっくりいうと data- と頭に付けた属性は基本的に(*1)すべて HTML5 の仕様として認められる、ということです。

使いどころは、要素にパラメータを持たせて JavaScript からアクセスするパターンです。

今回はサムネイルの要素に「どのメイン画像を表示させるか」というパラメータを持たせたかったので data 属性を利用しました。

「この要素がこの情報を持っていたら楽なのに」という場面はおそらく data 属性の出番です。

便利なのでこのパターンは覚えて損はないでしょう 🤠


以上、画像ギャラリーの実装クイズでした。


  1. 「基本的に」というのは、XML の属性名ルールおよび下記の制約に従う必要はあります。

    • 大文字小文字にかかわらず、名前を xml で始めてはならない。
    • 名前にセミコロン (U+003A) を含めてはならない。
    • 名前に大文字の A から Z までの文字を含めてはならない。

    data-* | MDN

    まぁ data-awesome-name のように普通に(xml 以外の)半角小文字とハイフンを使えば OK なので命名ルールはそんなに気にすることはないでしょう。


<!-- Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com --><!-- License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->