連載
» 2013年06月10日 18時00分 UPDATE

jQuery×HTML5×CSS3を真面目に勉強(3):Pinterest風グリッドレイアウトを作ってみよう (2/4)

[山田 直樹,クラスメソッド]

JavaScriptの実装1 - レイアウトの実現

 取りあえずプラグイン化といったものは後回しにして、先に動く所まで作っていきます。パラメータ等も現段階ではベタ書きで構いません。

 まずはカラム数を5に固定して実装してみます。

$(function() {
    // 画像読み込み完了後に実行     // ※補足1
    $(window).on('load', function() {
        // カラムのwidthを設定する
        colWidth = $('.grid').outerWidth() + offsetX * 2;
 
        // 最初にgridArrayを初期化
        gridArray = [];
        // 空のgridArrayを作成する
        for (var i=0; i<numOfCol; i++) {
            pushGridArray(i, 0, 1, -offsetY);
        }
 
        $('.grid').each(function(index) {
            setPosition($(this));
        });
    });
 
    var gridArray = [], // ※補足2
        colWidth,
        offsetX = 5,
        offsetY = 5,
        numOfCol = 5;
 
    // gridArrayに新しいgridを追加
    function pushGridArray(x, y, size, height) {
        for (var i=0; i<size; i++) {
            var grid = [];
            grid.x = x + i;
            grid.endY = y + height + offsetY * 2;
 
            gridArray.push(grid);
        }
    }
 
    // gridArrayから指定したx位置にあるgridを削除
    function removeGridArray(x, size) {
        for (var i=0; i<size; i++) {
            var idx = getGridIndex(x + i);
            gridArray.splice(idx, 1);
        }
    }
 
    // gridArray内にある高さの最小値と最大値および最小値のあるx値を取得
    function getHeightArray(x, size) {
        var heightArray = [];
        var temps = [];
        for (var i=0; i<size; i++) {
            var idx = getGridIndex(x + i);
            temps.push(gridArray[idx].endY);
        }
        heightArray.min = Math.min.apply(Math, temps);
        heightArray.max = Math.max.apply(Math, temps);
        heightArray.x = temps.indexOf(heightArray.min);
 
        return heightArray;
    }
 
    // gridのx値を基準にgridのインデックスを検索
    function getGridIndex(x) {
        for (var i=0; i<gridArray.length; i++) {
            var obj = gridArray[i];
            if (obj.x === x) {
                return i;
            }
        }
    }
 
    // gridを配置
    function setPosition(grid) {
        if(!grid.data('size') || grid.data('size') < 0) {
            grid.data('size', 1);
        }
 
        // gridの情報を定義
        var pos = [];
        var tempHeight = getHeightArray(0, gridArray.length);
        pos.x = tempHeight.x;
        pos.y = tempHeight.min;
 
        var gridWidth = colWidth - (grid.outerWidth() - grid.width());
 
        // gridのスタイルを更新     // ※補足3
        grid.css({
            'left': pos.x * colWidth,
            'top': pos.y,
            'position': 'absolute'
        });
 
        // gridArrayを新しいgridで更新
        removeGridArray(pos.x, grid.data('size'));
        pushGridArray(pos.x, pos.y, grid.data('size'), grid.outerHeight());
    }
 
    //IE用にArray.indexOfメソッドを追加  // ※補足4
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function(elt /*, from*/) {
            var len = this.length >>> 0;
 
            var from = Number(arguments[1]) || 0;
            from = (from < 0) ? Math.ceil(from) : Math.floor(from);
            if (from < 0) {
                from += len;
            }
 
            for (; from < len; from++) {
                if (from in this && this[from] === elt) {
                    return from;
                }
            }
            return -1;
        };
    }
});
JavaScript

 カラム数が固定のため、ウィンドウリサイズへの対応はまだですが、取りあえずここまででPinterest風のレイアウトは実現できました。結構ややこしいことになっているので、補足説明を入れておきます。

※補足1:loadイベント発火後に処理を実行する

 上記に書かれているJavaScriptコードは全体を$(function(){});で囲っていることから、onloadイベントのタイミングで処理が実行されています。しかし、onloadイベントとはHTMLコードがひと通り読み込まれ、JavaScriptが実行可能な状態になったタイミングで発火するイベントであり、この時点ではまだ画像ファイルは読み込みが完了していません。従ってこのタイミングで実行してしまうと各グリッドの最終的なheight値が得られないまま処理が走ってしまうため、期待通りのレイアウトにならなくなります。上記コードは全ての画像ファイルの読み込みが完了してから実行する必要があるため、windowオブジェクトのloadイベント発火時に実行させる必要があります

※補足2:配置済みのグリッドの情報を配列で管理する

 このレイアウトを実現する上で一番のキーポイントです。まずgridArrayという配列を定義します。

gridArray:Array

  • カラム数ぶんの長さを持つ
  • 配列には配置済みのグリッドのうち、各カラムの一番下に位置しているグリッドの情報が格納される
  • 配列の各要素には以下の情報が格納される
    1. x: 自分(※グリッド)が格納されている配列のインデックス
    2. endY: 自分(※グリッド)が配置されているカラムの上から下までの長さ(※距離)
image007.png 図3

 配列は図3のような構造をしています。5つのグリッド情報が配列に格納されており、これに6個目のグリッドを格納したいとします(※図4)。

image009.png 図4

 すでに格納されている5つのグリッド情報のうち、endYの値が最も小さく、かつ順番が若いのはgridArray[0]であり、グリッド6はここに格納します。

image011.png 図5

 よって図5のようにgridArray[0]には新たにグリッド6が格納され、endYの値も更新されます。このルーチンをひたすら繰り返すことで、次に来るグリッドがどのカラムのどの位置に配置されるかを算出していきます。

※補足3:グリッドを絶対配置する

 補足2の処理から取得したx値とy値でグリッドを絶対配置します。

※補足4:IEにArray.indexOfメソッドを追加する

 Internet Explorerには、Array.indexOfメソッドが実装されていません。そこで上記のコードを記述することでIEにindexOfメソッドを追加させます。

JavaScriptの実装2 - ウィンドウリサイズに対応

 ウィンドウ幅をリサイズした際にカラム数がウィンドウ幅に合わせて可変し、都度レイアウトが最適化されるような処理を追加していきます。

 コードを以下のように編集、追記します。

// 画像読み込み完了後に実行
$(window).on('load', function() {
    elements = $('#container');
    winObject = $(window);
 
    setCol();
    applyPinterestGrid();
 
    winObject.unbind('resize').resize(function() {
        var containerWidth;
        var winWidth = winObject.width() - offsetX * 2;
        if(winWidth < colWidth * numOfCol) {
            setCol();
            containerWidth =  colWidth * (numOfCol - 1);
        } else if (winWidth > colWidth * (numOfCol + 1)) {
            setCol();
            containerWidth =  colWidth * (numOfCol + 1);
        }
        if (containerWidth) {
            var current = elements.width();
            elements.width(colWidth * numOfCol);
            applyPinterestGrid();
        }
    });
});
 
var gridArray = [],
    colWidth,
    offsetX = 5,
    offsetY = 5,
    numOfCol = 5,
    elements,
    winObject;
 
// Pinterest風グリッドレイアウト適用
function applyPinterestGrid() {
    // 最初にgridArrayを初期化
    gridArray = [];
    // 空のgridArrayを作成する
    for (var i=0; i<numOfCol; i++) {
        pushGridArray(i, 0, 1, -offsetY);
    }
 
    $('.grid').each(function(index) {
        setPosition($(this));
    });
 
    //最後にエレメントの高さを設定
    var heightarr = getHeightArray(0, gridArray.length);
    elements.height(heightarr.max + offsetY);
}
 
// カラムの数とwidthを設定する
function setCol() {
    colWidth = $('.grid').outerWidth() + offsetX * 2;
    numOfCol = Math.floor((winObject.width() - offsetX * 2) / colWidth);
}
... 以下はそのまま
JavaScript

 ウィンドウのリサイズイベントにてContainer要素とWindowオブジェクトの幅を取得します。そしてそこから表示可能なカラム数を算出して、レイアウトを適用します。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。