WebDesign Dackel

OwlCarouselとPhotoSwipeでモバイルフレンドリーなギャラリーを作る

OwlCarouselとPhotoSwipeでモバイルフレンドリーなギャラリーを作る

Hatena0
Google+0
Pocket0
Feedly0

はじめに

OwlCarouselPhotoSwipe、どちらも細かい制御が出来てモバイル環境での動作も非常に軽快です。僕もよくお世話になるお気に入りのライブラリです。

それぞれ別々で使うことはありましたが、カルーセル部分にOwlCarousel、モーダル表示にPhotoSwipeの様に連携したギャラリーというものを作ったことが無かったので今回サンプルを作ってみました。

動作サンプル

まずはじめに、この記事で作るサンプルの動作から確認していきます。

OwlCarouselとPhotoSwipeの動作サンプル画像

動作サンプルはこちら

2つのギャラリーがあり、画像をタップ(クリック)するとうひょーんとLightBoxみたいに表示されます。
2つ目のギャラリーには、それぞれの画像に対してキャプションも付いています。

サンプルのポイント

  • 複数のギャラリーに対応
  • PhotoSwipe上で画像を切り替えた時にOwlCarouselの位置も調整する

上記2点が今回作るサンプルのポイントになってきます。

実装準備

ガシガシとコードを書いていく前に事前準備です。

必要なファイルを持ってくる

それぞれ必要なファイルをダウンロードしていきます。リンク先+念のため今回サンプルで使用したバージョンを書いておきました。
OwlCarouselPhotoSwipeは勿論ですが、OwlCarouseljQueryのプラグインなのでそちらもお忘れなく。

jQuery

使用したバージョン: 1.11.3
https://jquery.com/download/

OwlCarousel

使用したバージョン: 1.3.3
http://owlgraphic.com/owlcarousel/

PhotoSwipe

使用したバージョン: 4.1.0
https://github.com/dimsemenov/photoswipe
(今回はリポジトリのDownload ZIPから落としてきました)

ファイル構成

持ってきたファイルから必要なファイルだけ選んで、下記のように展開しました。

.
├── css
│   ├── AjaxLoader.gif
│   ├── default-skin
│   │   ├── default-skin.css
│   │   ├── default-skin.png
│   │   ├── default-skin.svg
│   │   └── preloader.gif
│   ├── grabbing.png
│   ├── owl.carousel.css
│   ├── photoswipe.css
│   └── style.css
├── images
│   ├── photo01.jpg
│   ├── photo01_lg.jpg
│   ├── # ... 1~15の画像を用意しました
│   ├── photo15.jpg
│   └── photo15_lg.jpg
├── index.html
└── js
    ├── app.js
    ├── jquery-1.11.3.min.js
    ├── owl.carousel.min.js
    ├── photoswipe-ui-default.min.js
    └── photoswipe.min.js
  • ./css/style.css
  • ./js/app.js
  • ./index.html

上記3つのファイルは、それぞれ自分で書いていくものになるので新規作成しておきます。

OwlCarouselとPhotoSwipeの参考サイト

今回は基本的な使い方は書きませんので、そもそも「使い方分かんないよー!」な場合は下記のサイトが参考になると思います。

OwlCarouselの参考サイト

PhotoSwipeの参考サイト

全体のコード

HTML&CSS

ところどころ必要の無い部分も含まれますが、とりあえず全部載せておこうと思います。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <link rel="stylesheet" href="./css/owl.carousel.css">
  <link rel="stylesheet" href="./css/photoswipe.css">
  <link rel="stylesheet" href="./css/default-skin/default-skin.css">
  <link rel="stylesheet" href="./css/style.css">
</head>
<body>
  <div class="container">
    <h1>OwlCarousel & PhotoSwipe</h1>

    <h2>First gallery</h2>
    <ul class="owl-carousel">
      <li><a href="./images/photo01_lg.jpg" data-size="960x640"><img src="./images/photo01.jpg" alt="写真1"></a></li>
      <li><a href="./images/photo02_lg.jpg" data-size="960x640"><img src="./images/photo02.jpg" alt="写真2"></a></li>
      <li><a href="./images/photo03_lg.jpg" data-size="960x640"><img src="./images/photo03.jpg" alt="写真3"></a></li>
      <li><a href="./images/photo04_lg.jpg" data-size="960x640"><img src="./images/photo04.jpg" alt="写真4"></a></li>
      <li><a href="./images/photo05_lg.jpg" data-size="960x640"><img src="./images/photo05.jpg" alt="写真5"></a></li>
      <li><a href="./images/photo06_lg.jpg" data-size="960x640"><img src="./images/photo06.jpg" alt="写真6"></a></li>
      <li><a href="./images/photo07_lg.jpg" data-size="960x640"><img src="./images/photo07.jpg" alt="写真7"></a></li>
      <li><a href="./images/photo08_lg.jpg" data-size="960x640"><img src="./images/photo08.jpg" alt="写真8"></a></li>
      <li><a href="./images/photo09_lg.jpg" data-size="960x640"><img src="./images/photo09.jpg" alt="写真9"></a></li>
    </ul>

    <h2>Second gallery</h2>
    <ul class="owl-carousel">
      <li><a href="./images/photo10_lg.jpg" data-size="960x640" data-title="タイトル1"><img src="./images/photo10.jpg" alt="写真10"></a></li>
      <li><a href="./images/photo11_lg.jpg" data-size="960x640" data-title="タイトル2"><img src="./images/photo11.jpg" alt="写真11"></a></li>
      <li><a href="./images/photo12_lg.jpg" data-size="960x640" data-title="タイトル3"><img src="./images/photo12.jpg" alt="写真12"></a></li>
      <li><a href="./images/photo13_lg.jpg" data-size="960x640" data-title="タイトル4"><img src="./images/photo13.jpg" alt="写真13"></a></li>
      <li><a href="./images/photo14_lg.jpg" data-size="960x640" data-title="タイトル5"><img src="./images/photo14.jpg" alt="写真14"></a></li>
      <li><a href="./images/photo15_lg.jpg" data-size="960x640" data-title="タイトル6"><img src="./images/photo15.jpg" alt="写真15"></a></li>
    </ul>
  </div>

  <script type="text/javascript" src="./js/jquery-1.11.3.min.js"></script>
  <script type="text/javascript" src="./js/owl.carousel.min.js"></script>
  <script type="text/javascript" src="./js/photoswipe.min.js"></script>
  <script type="text/javascript" src="./js/photoswipe-ui-default.min.js"></script>
  <script type="text/javascript" src="./js/app.js"></script>
</body>
</html>

ギャラリー部分のHTMLはとにかくシンプルにしたかったので、ul.owl-carouselで画像付きリストをつくるだけにしています。
<a>data属性に、次のように画像の情報をつけています。

  • data-size: PhotoSwipeで表示する画像のサイズ(x区切りで横幅と縦幅)
  • data-title: キャプション(任意)

では次にCSSです。
./css/style.cssに書いていきます。

@charset "utf-8";

* {
  margin:0;
  padding:0;
}

html,
body {
  color:#869690;
  font-size:18px;
  font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
}

h1 {
  margin:2em 0;
  font-size:1.8em;
  text-align:center;
}

h2 {
  margin:1.5em 0 .5em 0;
  font-size:1.4em;
  text-align:center;
}

/* ここからがギャラリー用のスタイルです! */
img {
  max-width:100%;
  height:auto;
}

.container {
  width:960px;
  margin:0 auto;
  padding-bottom:60px;
}

.owl-carousel li {
  list-style:none;
}

.owl-carousel li img {
  border-radius:5px;
  transition:transform .15s ease-out;
}

.owl-carousel li img:hover {
  transform:scale(.98, .98);
}

.owl-carousel li img:active {
  transform:scale(.96, .96);
}


@media only screen and (max-width: 960px) {
  .container {
    width:auto;
    padding-right:15px;
    padding-left:15px;
  }

  .owl-carousel li {
    padding-right:5px;
    padding-left:5px;
  }
}

CSSも書いてみたものの、動作にはあまり関係ないです。。笑
ここは好きなデザインに調整してもらえたらと思います。

JavaScript

用意しておいた、./js/app.jsに書いていきます。
細かい説明はしませんが、コメントアウトを参照してもらえれば大体分かるかなと思います!

$(function(){

  // PhotoSwipe用のHTMLを描画
  function buildPswdHtml(){
    $("body").append([
      '<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">',
      '  <div class="pswp__bg"></div>',
      '  <div class="pswp__scroll-wrap">',
      '    <div class="pswp__container">',
      '      <div class="pswp__item"></div>',
      '      <div class="pswp__item"></div>',
      '      <div class="pswp__item"></div>',
      '    </div>',
      '    <div class="pswp__ui pswp__ui--hidden">',
      '      <div class="pswp__top-bar">',
      '          <div class="pswp__counter"></div>',
      '          <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>',
      '          <button class="pswp__button pswp__button--share" title="Share"></button>',
      '          <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>',
      '          <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>',
      '          <div class="pswp__preloader">',
      '            <div class="pswp__preloader__icn">',
      '              <div class="pswp__preloader__cut">',
      '                <div class="pswp__preloader__donut"></div>',
      '              </div>',
      '            </div>',
      '          </div>',
      '      </div>',
      '      <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">',
      '        <div class="pswp__share-tooltip"></div> ',
      '      </div>',
      '      <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>',
      '      <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>',
      '      <div class="pswp__caption">',
      '        <div class="pswp__caption__center"></div>',
      '      </div>',
      '    </div>',
      '  </div>',
      '</div>'
    ].join(""));
  }


  // ギャラリーから、PhotoSwipe用のitemsを取得
  function getGalleryItems($gallery){
    var items = [];

    $gallery.find("a").each(function(){
      var $anchor = $(this),
          size = $anchor.attr("data-size").split("x"),
          title = $anchor.attr("data-title"),
          item = {
            el: $anchor.get(0),
            src: $anchor.attr("href"),
            w: parseInt(size[0]),
            h: parseInt(size[1])
          };

      // キャプション
      if( title ) item.title = title;

      items.push(item);
    });

    return items;
  }


  // PhotoSwipeを開く
  function openGallery($gallery, index, items, pswpOptions){
    var $pswp = $(".pswp"),
        owl = $gallery.data("owlCarousel"),
        gallery;

    // オプション値を設定
    var options = $.extend(true, {
      // 開く画像番号
      index: index,

      // 画像クリック時のズーム設定
      getThumbBoundsFn: function(index){
        var $thumbnail = $(items[index].el).find("img"),
            offset = $thumbnail.offset();
        return {
          x: offset.left,
          y: offset.top,
          w: $thumbnail.outerWidth()
        };
      }
    }, pswpOptions);

    // PhotoSwipeを表示
    gallery = new PhotoSwipe($pswp.get(0), PhotoSwipeUI_Default, items, options);
    gallery.init();

    // PhotoSwipe側の切り替えに応じて、OwlCarouselも位置を調整する
    gallery.listen("beforeChange", function(x){
      owl.goTo(this.getCurrentIndex());
    });

    gallery.listen("close", function(){
      this.currItem.initialLayout = options.getThumbBoundsFn(this.getCurrentIndex());
    });
  }


  // 初期化
  function initializeGallery($elem, owlOptions, pswpOptions){

    // PhotoSwipe用のDOMが存在しない場合、新しく描画
    if( $(".pswp").size() === 0 ){
      buildPswdHtml();
    }

    // 複数のギャラリーに対応するために走査
    $elem.each(function(i){
      var $gallery = $(this),
          uid = i + 1,
          items = getGalleryItems($gallery),
          options = $.extend(true, {}, pswpOptions);

      // OwlCarouselの初期化
      $gallery.owlCarousel(owlOptions);

      // 各ギャラリーに対してユニークなIDを割り当てる
      options.galleryUID = uid;
      $gallery.attr("data-pswp-uid", uid);

      // 各アイテムのクリックで、PhotoSwipeを起動
      $gallery.find(".owl-item").on("click", function(e){
        if( !$(e.target).is("img") ) return;

        // itemsはPhotoSwipe.init()に書き換えられるのでコピーを渡す
        openGallery($gallery, $(this).index(), items.concat(), options);
        return false;
      });
    });
  }


  // サンプルでは`.owl-carousel`に対して処理を実行する
  var owlOptions = {
        itemsCustom: [[0, 3]],
        responsiveRefreshRate: 0
      },
      pswpOptions = {
        bgOpacity: 0.9,
        history: false,
        shareEl: false
      };

  initializeGallery($(".owl-carousel"), owlOptions, pswpOptions);

});

OwlCarouselとPhotoSwipeの連携時のポイント

一番のポイントとなるのは、ギャラリーを表示している下記の部分です。

// PhotoSwipeを表示
gallery = new PhotoSwipe($pswp.get(0), PhotoSwipeUI_Default, items, options);
gallery.init();

// PhotoSwipe側の切り替えに応じて、OwlCarouselも位置を調整する
gallery.listen("beforeChange", function(x){
  owl.goTo(this.getCurrentIndex());
});

gallery.listen("close", function(){
  this.currItem.initialLayout = options.getThumbBoundsFn(this.getCurrentIndex());
});

PhotoSwipeの切り替えのタイミングを監視して、背景にあるOwlCarouselの位置も更新するようにしています。

しかしこのままでは、PhotoSwipeを閉じて画像がズームアウトしていく際の位置がずれてしまいました。そのため、PhotoSwipeを閉じるタイミングでズームアウト位置を取得するようにしています。(currItemが現在の要素、initialLayoutがズームアップ・アウトの位置情報が入っているみたいでした)

まとめ

今回のサンプルではシェアボタンの設定や、固有のURLからPhotoSwipeを起動させたり、みたいな機能は省いてしまっているので必要に応じてそこらへんは調整する必要がありそうです。


OwlCarouselPhotoSwipeを使ってみましたが、モバイルでの使い勝手を考えるとなかなか良い組み合わせなんじゃないかなと思いました。