カテゴリー
CSS HTML jQuery Webサイト作成

ギャラリーページ(3)

こんにちは、エムケーワイです。

今回はギャラリーページの第3弾です。

第1弾ではJavaScriptライブラリImagesLoadedとMasonryを使って、20種類の画像のロード完了を待って、それらをタイル状に配置・表示するギャラリーページを作成しました。画像クリックによる拡大画像のモーダル表示も実装しました。

第2弾では画像を89種類に増やし、画像を16種類ずつ追加表示していく機能を追加実装しました。

第3弾では更に画像をカテゴリー別に表示する機能を実装します。

では、早速結果を見ていただきます。

    第2弾に対する主な追加・修正ポイントは下記になります。

    1. カテゴリーを選択するためのラジオボタンを追加。
    2. 各カテゴリーが選択された際、JSONデータから選択されたカテゴリーの要素のみを取得(フィルタリング)、表示する処理を追加。

    では、実現方法を説明して行きます。

    目次 [閉じる]
    1. HTMLコード
    2. JSONファイル
    3. JavaScriptライブラリの読み込み
    4. jQueryコード
    5. CSS設定

    1. HTMLコード

    今回カスタムHTMLで記述したコードは下記の通りです。

    第2弾のコードの前にカテゴリー選択用ラジオボタンを配置するためのコードを追記しました。

    <div class="radioButtons">
        <form class="cateFilter" id="cateFilter">
            <span class="cateItem">
                <input type="radio" name="filter" id="cateAll" value="all" checked="">
                <label for="cateAll">All</label>
            </span>
            <span class="cateItem">
                <input type="radio" name="filter" id="cateShenzhen" value="Shenzhen">
                <label for="cateShenzhen">Shenzhen</label>
            </span>
            <span class="cateItem">
                <input type="radio" name="filter" id="cateHongkong" value="HongKong">
                <label for="cateHongkong">Hong Kong</label>
            </span>
            <span class="cateItem">
                <input type="radio" name="filter" id="cateVietnam" value="Vietnam">
                <label for="cateVietnam">Vietnam</label>
            </span>
            <span class="cateItem">
                <input type="radio" name="filter" id="cateFlowers" value="Flowers">
                <label for="cateFlowers">Flowers</label>
            </span>
            <span class="cateItem">
                <input type="radio" name="filter" id="cateFireworks" value="Fireworks">
                <label for="cateFireworks">Fireworks</label>
            </span>
        </form>
    </div>
    
    <div class="mkyGalleryContainer">
        <ul class="mkyGallery"></ul>
        <button class="loadMore" id="loadMore">Load More</button>
    </div>

    ラジオボタン配置のコードについて以下に説明します。

    1. 全体をクラス”radioButtons”を持つdiv要素で囲みます。
    2. カテゴリーを選択するためのform要素として6つのラジオボタンを配置します。ラジオボタンのvalue属性には、JSONデータのプロパティ”category”の値(“Shenshen”/”HongKong”/”Vietnam”/”Flowers”/”Fireworks”)を設定します。“all”は全カテゴリー選択を意味します。初期状態では”all”に選択状態であることを示すchecked属性が付加されています。
    3. 各ラジオボタンの横に、対応するカテゴリーを示すラベルを表示します。

    ラジオボタンのvalue属性値とJSONデータの”category”プロパティの値を紐づけることで、フィルタリングの際JSONデータから選択されたカテゴリーの要素のみを取得することが可能になります。

    2. JSONファイル

    今回も第2弾と同じく”galleryFull.json”を使用します。

    ようやく”category”プロパティの出番です!

    [
      {
        "title": "Night View",
        "category": "HongKong",
        "images": {
          "thumb": "/wp-content/themes/twentytwenty_ch/gallery/images/thumb-H-8.jpg",
          "large": "/wp-content/themes/twentytwenty_ch/gallery/images/large-H-8.jpg"
        }
      },
      {
        "title": "Victoria Harbor",
        "category": "HongKong",
        "images": {
          "thumb": "/wp-content/themes/twentytwenty_ch/gallery/images/thumb-H-4.jpg",
          "large": "/wp-content/themes/twentytwenty_ch/gallery/images/large-H-4.jpg"
        }
      },
      { ... }
    ]

    3. JavaScriptライブラリの読み込み

    第3弾でもJavaScriptライブラリImagesLoadedとMasonryを使用します。

    第1弾、第2弾と同じようにfunctions.phpで上記ライブラリの読み込み設定を行っています。

    4. jQueryコード

    今回作成したjQueryコードは下記になります。

    第2弾のjQueryコードに対して追加・修正した部分をメインに説明します。

    jQuery(function() {
        jQuery('.mkyGallery').each(function() {
            var _gallery = jQuery(this),    // ギャラリーオブジェクト
                _loadMoreButton = jQuery('#loadMore'),    // 追加ボタン
                _radioButtons = jQuery('.radioButtons'),  // ラジオボタンオブジェクト
                _filter = jQuery('#cateFilter'),    // フィルターオブジェクト
                addImgCount = 16,      // クリック毎に追加する画像数
                imgCount = 0,          // 現在の表示画像数
                jsonData = [],         // JSONデータ
                filteredData = [],     // フィルタリングされたJSONデータ
    	    galleryW = _gallery.width(),    // ギャラリー領域の幅
    	    imageW = 300,	   // 画像の幅
    	    gutterW;		   // gutterの幅
    			
    	// ラジオボタンを含むブロックの高さを設定
    	_radioButtons.css('height', _filter.outerHeight());
    
    	// gutterの幅を算出
    	if (galleryW > 900) {
    	    gutterW = Math.floor((galleryW - imageW * 3) / 2);
    	} else if (galleryW > 600) {
    	    gutterW = galleryW - imageW * 2;
    	} else {
    	    gutterW = galleryW - imageW;
    	}
    		
    	// Masonry初期設定
            _gallery.masonry({
                columnWidth: 300,
                gutter: gutterW,
                itemSelector: '.mkyGalleryItem'
            });
    
            // JSONデータを取得し、ギャラリーを生成する
            jQuery.getJSON('/wp-content/themes/twentytwenty_ch/gallery/galleryFull.json', function(data) {
                // 取得した JSON データを格納
                jsonData = data;
                // 初期状態はフィルタリングせず全データを代入
                filteredData = jsonData;
    
                // ギャラリーのHTMLコードを生成し、画像を表示する
                createHTML();
                // Load Moreボタンクリックで画像を追加
                _loadMoreButton.on('click', createHTML);
                // カテゴリーが選択されたらフィルタリングを実行
                _filter.on('change', 'input[type="radio"]', filterItems);
            });
    
            // HTMLコードを生成しギャラリーを表示する
            function createHTML(filter) {
                var elements = [],
                	slicedData = filteredData.slice(imgCount, imgCount + addImgCount);
                
                // JSON配列内の要素毎に処理する
                jQuery.each(slicedData, function (i, item) {
                	// HTMLコード(文字列)を生成する
                    var htmlCode =
                            '<li class="mkyGalleryItem onLoading">' +
                            '<a class= "mkyJsModalOpen" href="' + item.images.large + '">' +
                            '<img src="' + item.images.thumb + '" alt="">' +
                            '</a>' +
                            '<div class="mkyModal mkyJsModal" style="display: none;">' +
                            '<div class="mkyModalBG"></div>' +
                            '<div class="mkyModalContent">' +
                            '<figure>' +
                            '<img src="' + item.images.large + '" alt="">' +
                            '<span class="caption">' +
                            '<b class="title">' + item.title + '</b>' +
                            '</span>' +
                            '<button class="mkyJsModalClose">×</button>' +
                            '</figure>' +
                            '</div>' +
                            '</div>' +
                            '</li>';
                    // HTMLコード(文字列)をDOM要素化し、配列に追加
                    elements.push(jQuery(htmlCode).get(0));
                });
    
                // DOM要素の配列をコンテナーに挿入し、Masonryを実行
                _gallery
                    .append(elements)
                    .imagesLoaded(function() {
                        jQuery(elements).removeClass('onLoading');
                        _gallery.masonry('appended', elements);
    
                        // フィルタリング時は再配置
                        if (filter) {
                            _gallery.masonry();
                        }
                    });
    
                // 現在の表示画像数の更新
                imgCount += slicedData.length;
    
                // 全画像が表示されたらLoadMoreボタンを消去
                if (imgCount < filteredData.length) {
                    _loadMoreButton.show();
                } else {
                    _loadMoreButton.hide();
                }
    
        	    // 画像のモーダル表示
       	    jQuery('.mkyGalleryItem').on('click',function(){
           	        jQuery(this).find('.mkyJsModal').fadeIn();
    	        return false;
    	    });
    	    jQuery('.mkyJsModalClose').on('click',function(){
                    jQuery(this).parents('.mkyJsModal').fadeOut();
                    return false;
    	    });
            }
            
            // カテゴリーフィルター
            function filterItems() {
                var key = jQuery(this).val(),   // 選択されたラジオボタンのvalue
                	// 表示済みのMasonryアイテム
                	masonryItems = _gallery.masonry('getItemElements');
    
                // Masonryアイテムを削除
                _gallery.masonry('remove', masonryItems);
                // フィルタリング後データ、現在の表示画像数をクリア
                filteredData = [];
                imgCount = 0;
    
                if (key === 'all') {
                    // allが選択された場合、全JSONデータを格納
                    filteredData = jsonData;
                } else {
                    // all以外の場合、keyと一致する要素を抽出
                    filteredData = jQuery.grep(jsonData, function (item) {
                        return item.category === key;
                    });
                }
    
                // HTMLコードを生成し、要素を表示
                createHTML(true);
            }
        });
    });

    4-1. 全体の流れ

    全体の流れは第2弾とほぼ同じですが、下記の追加・修正を行っています。

    • カテゴリー選択時のフィルタリング処理を追加
    • HTMLコードの生成、要素の配置・表示を、フィルタリングされたJSONデータを対象とするよう修正

    4-2. 変数の宣言、初期化

    第1弾に対し下記の変数が追加されています。

    変数意味
    _radioButtonsラジオボタンオブジェクト
    _filterカテゴリーフィルターオブジェクト
    filteredDataフィルタリング後のJSONデータを格納する変数

    4-3. ラジオボタンブロックの高さ設定

    ラジオボタンを囲むdiv要素の高さとして、内部要素(_filter)のボーダーラインを含めた高さ(outerHeight())を設定しています。これは、ラジオボタン上にギャラリーの画像が表示されないようにするためです。

    _radioButtons.css('height', _filter.outerHeight());

    4-4. Masonryの初期設定

    第1弾、第2弾と同じです。

    4-5. JSONデータの取得とギャラリー生成

    jQuery.getJSON('/wp-content/themes/twentytwenty_ch/gallery/galleryFull.json', function(data) {
        // 取得した JSON データを格納
        jsonData = data;
        // 初期状態はフィルタリングせず全データを代入
        filteredData = jsonData;
        // ギャラリーのHTMLコードを生成し、画像を表示する
        createHTML();
        // Load Moreボタンクリックで画像を追加
        _loadMoreButton.on('click', createHTML);
        // カテゴリーが選択されたらフィルタリングを実行
        _filter.on('change', 'input[type="radio"]', filterItems);
    });

    第2弾から追加・修正された部分について説明します。

    1. getJSON()メソッドで取得したデータを、変数jsonDataと共に変数filteredDataにも代入しています。jsonDataに全データを保存しておき、filteredDataがHTMLコード生成、要素の配置・表示の対象となります。
    2. 選択カテゴリーが変わったら(changeイベント発生)、フィルタリング処理を行う関数filterItems()を呼び出します。

    4-6. HTMLコードの生成と画像の配置・表示

    関数createHTML()に対する追加・修正について説明します。

    4-6-1. 引数の追加

    カテゴリーが変更された場合は、Masonryで要素の再配置を行う必要があります。そのためフィルタリングが行われたかどうかを示す引数filter(trueでフィルタリングあり)を追加しました。

    引数なしで呼び出された場合はfalseと判断されます。

    4-6-2. HTMLコードの生成

    slice()メソッドの対象をjsonDataからfilteredDataに変更しています。

    slicedData = filteredData.slice(imgCount, imgCount + addImgCount);

    HTMLコード生成処理、HTMLコード(文字列)をDOM要素化して配列elementsに追加する処理は第1弾、第2弾と同じです。

    4-6-3. 画像の配置、表示

    引数filterがtrueの場合、Masonryで要素の再配置を行う処理を追加しました。再配置を実行しないと、それまで要素が表示されていた領域が空白となり、その後ろにフィルタリングされた要素が追加されてしまいます。

    _gallery
        .append(elements)
        .imagesLoaded(function() {
            jQuery(elements).removeClass('onLoading');
            _gallery.masonry('appended', elements);
            // フィルタリング時は再配置
            if (filter) {
                _gallery.masonry();
            }
        });

    4-6-4. 「Load More」ボタンの表示・消去

    カテゴリーが変更されると、HTMLコードの生成、要素の配置・表示をやり直すことになるので、「Load More」ボタンの表示処理を追加しています。

    if (imgCount < filteredData.length) {
        _loadMoreButton.show();
    } else {
        _loadMoreButton.hide();
    }

    4-7. フィルタリング

    選択カテゴリーが変更された際のフィルタリング処理は関数filterItems()にまとめています。

    function filterItems() {
        var key = jQuery(this).val(),   // チェックされたラジオボタンのvalue
            // 追加済みの Masonry アイテム
            masonryItems = _gallery.masonry('getItemElements');
    
        // Masonry アイテムを削除
        _gallery.masonry('remove', masonryItems);
        // フィルタリング後のデータと現在の表示画像数をクリア
        filteredData = [];
        imgCount = 0;
    
        if (key === 'all') {
            // allが選択された場合、すべてのJSONデータを格納
            filteredData = jsonData;
        } else {
            // all以外の場合、キーと一致するデータを抽出
            filteredData = jQuery.grep(jsonData, function (item) {
                return item.category === key;
            });
        }
    
        // アイテムを追加
        createHTML(true);
    }
    1. 選択されたカテゴリー(ラジオボタン)のvalue属性値を変数keyに代入します。
    2. Masory()メソッドで、現在ギャラリーに表示されている要素を取得し、変数masonryItemsに代入します。
    3. Masonry()メソッドで、ギャラリーから表示要素(masonryItems)を削除します。
    4. フィルタリング後のデータ(filteredData)と現在の表示画像数(imgCount)をクリアします。
    5. カテゴリー”all”が選択された場合、変数filteredDataに全JSONデータ(jsonData)を格納します。
    6. “all”以外のカテゴリーが選択された場合、全JSONデータ(jsonData)から選択されたカテゴリーの要素を抽出し、変数filteredDataに格納します。
      具体的にはjQuery.grep()メソッドを使って、jsonDataからプロパティ”category”の値がkeyと一致する要素を取得しています。

    jQuery.grep()メソッドでは、第1引数に処理対象の配列を、第2引数に抽出条件を関数の形で指定します。

    関数には引数として配列の要素(item)が渡されます。関数の中身は”return 条件”で、条件を満たし、trueが返された要素のみがfilteredDataに格納されます。今回の条件は”item.category === key”となります。

    ちょっと特殊な書き方ですね。

    5. CSS設定

    ラジオボタンに対する設定を追加しました。

    /*
    * radio buttons
    */
    .radioButtons {
        border-bottom: 1px solid gray;
        height: auto;
    }
    
    /* category filter */
    .cateFilter {
        float: right;
    }
    .cateFilter label {
        cursor: pointer;
        display: inline-block;
        font-weight: bold;
        padding: 0 4px 0 0;
    }
    .cateFilter span {
        display: inline-block;
    }
    .cateFilter span:not(:last-child) {
        margin-right: 10px;
    }
    1. クラス”radioButtons”(ラジオボタンを囲むdiv要素)に対しアンダーラインを引きます。高さ(height)に”auto”を設定していますが、jQueryで数値が設定されます。
    2. クラス”cateFilter”に対して、”float :right”を設定しています。これにより、ラジオボタンは右詰めで配置されます。

    いかがでしたでしょうか?

    ギャラリーページの第3弾として、ラジオボタンでギャラリー要素のカテゴリーを選択できるようにし、選択されたカテゴリーの要素のみ配置・表示するようにしました。

    今回の肝は、jQuery.grep()メソッドを使って、配列内の指定条件を満たす要素を抽出(フィルタリング)する処理でしたね。

    フィルタリング処理は関数化し、フィルタリング後関数createHTML()を呼び出しています。HTMLコードの生成、要素の配置・表示を関数化したことによって、本ページを表示する際も、「Load More」ボタンクリック時も、カテゴリー変更時も同じ関数を使ってギャラリーを生成、表示できます。なお、関数createHTML()に引数を追加して、カテゴリー変更時はMasonry()メソッドで要素の再配置を行っています。

    今回説明を省略した部分については第1弾の投稿第2弾の投稿をご参照ください。

    これでギャラリーページへのトライを終了します。初めてImagesLoaded、Masonryという2つのJavaScriptライブラリを使用しましたが、便利なライブラリが用意されているものですね。もっと色々調べてみる必要がありそうです。

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