画像クリックでスライドショーを表示したい
photoswipeを導入して、画像をクリックした際に高解像度の画像をスライドショー表示できるようにしておきました。

やりたいこと

本ブログでは表示速度を優先し(?)、最低限の解像度を持つ画像をレスポンシブに配信しています。

しかしながら特にスマホにおいて、画像を拡大するとその粗さが気になりだしました。

そこでタイトルの通り、photoswipeを導入し、画像がクリックされた際に高解像度の画像のスライドショーが表示されるようにしてみました。

とにかくはじめます🍛

photoswipe

今回はphotoswipeと呼ばれるJavascript製のライブラリを使用します。

何ができるかは公式サイトを見れば良く分かります。

https://photoswipe.com/

一部条件はありますが、基本的にはMITライセンスとして提供されています。

The script is free to use for personal and commercial projects. It falls under the MIT license with one exception: Do not create a public WordPress plugin based on it, as I will develop it.

同様の機能を提供するライブラリとしては fancybox が有名です。 ただしこちらは jQuery 必須であり、パフォーマンスの懸念があったため採用は見送りました。

導入

基本的にはこちらの手順通りで問題ないはずです。

ライブラリのインストール

私はnpmを利用たのでこれだけ。

$ npm install photoswipe --save

Hugoの設定

imgタグ

基本的にはphotoswipe化したい画像に対して、data-size属性で画像サイズを格納しているだけです。

以下のimage.htmlは私が独自に作成している画像出力用のテンプレートですが、大まかなイメージは以下の通りです。

image.html
{{ $imgSrc := $image.RelPermalink }}
{{ $imgWidth := $image.Width }}
{{ $imgHeight := $image.Height }}
<img src="{{ $imgSrc }}" data-size="{{ printf "%dx%d" $imgWidth $imgHeight }}"/>

実際にはレスポンシブ対応などが入っていますが、簡略化しています。

表示用DOMの作成

phtoswipeによるギャラリー画像を表示するためのコンテナを用意します。

phtoswipeに自動的に作成してほしいことろですが、あえてユーザに任せることでライブラリのサイズを小さく保ち、かつ柔軟なカスタマイズを可能にしているようです。

実際には公式のテンプレートをそのままコピペでも十分です。

photoswipe.html
<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->
                <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>

                <!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <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>

このテンプレートを/layouts/_default/single.htmlに差し込んでおきました。

/layouts/_default/single.html
{{ define "main" }}
 :
 略
{{ partial "photoswipe.html" . }}
{{ end }}

ちなみにsingle.htmlはHugoでは記事に適用されるテンプレートファイルです。1

つまりTop画面や一覧画面ではphotoswipeは利用しない方針としました。

javascript

javascriptにより画像がクリックされた際にギャラリーが表示されるようにします。

処理内容はコードのコメントに記載した通りです。

pswp.js
import 'photoswipe/dist/photoswipe.css'
import 'photoswipe/dist/default-skin/default-skin.css'

import PhotoSwipe from 'photoswipe/dist/photoswipe.min.js'
import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default.min.js'

const setup = () => {
  // 画像リストを作成
  const items = Array.from(document.querySelectorAll('img[data-size]')).map(i => {
    const wh = i.getAttribute('data-size').split('x')
    return {
      src: i.getAttribute('src'),
      w: parseInt(wh[0], 10),
      h: parseInt(wh[1], 10)
    }
  })
  // photoswipe.htmlで定義したルート
  const pswpElement = document.querySelector('.pswp')
  // 画像クリック時にギャラリーが表示されるようにする
  document.querySelectorAll('img[data-size]').forEach((e, i) => {
    e.onclick = () => {
      const gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
        shareEl: false,
        index: i
      })
      gallery.init()
    }
  })
}
setup()

結果

実際にクリックしてみてください。

sample.jpg

パフォーマンス改善

ちょっと気になるのはphotoswipe自体のモジュールサイズです。

導入前から比較した、追加容量はだいたい以下でした。(gzip圧縮前)

typesize
js+42K
css+10K
image+3K

jQueryなどに比べるともちろん容量は少ないですが、それでもやはりこの増加量は気になります。2

少しでも減らすためにcssに関しては未使用のスタイル(sns表示とコメント表示)を PurgecssPlugin により削っていますが、せいぜい2Kほどしか削減できませんでした。

サイズを減らす以外にもっと有効な方法としては、遅延ローディングがあります。

要するにphotoswipeは画像をクリックした際に必要となるだけで、ブログの記事を表示するうえでは全く役に立ちません。 言い換えると、画像がクリックされて初めて上記のモジュールが必要となります。

よってphotoswipeのモジュールは、画像が初めてクリックされた際にダウンロードされるように修正します。

と言っても主要な変更はこれぐらいです。

photoswipe.html
:
略

{{ $scripts := resources.Get "output/js/pswp.js" | fingerprint }}
{{ with $scripts }}
  <script type="text/javascript">
    document.querySelectorAll('img[data-size]').forEach(img => {
      img.style.cursor = 'pointer'
      img.onclick = (event) => {
        // 初回クリックでモジュールをダウンロード
        import("{{ .Permalink | relURL }}").then(() => {
          // ダウンロード完了後改めてクリック
          event.target.click()
        })
      }
      //-> このonclickはpswp.jsロード後に上書きされる
    })
  </script>
{{end}}

開発者ツールで確認

記事の表示直後の状態では、縮小された画像がダウンロードされています(赤点線)

devtool.png

そして画像をクリックした際に、オリジナル画像およびphotoswipeのモジュールがダウンロードされます(赤点線)

devtool2.png
  • pswp.xxxxphotoswipe関連のモジュール
    • 理論的にはjs(42K)+css(10K)=52Kになるはずですが、webpackによりバンドル化しているので若干オーバヘッド分が含まれます
  • default-skin.svgはcssから参照されている画像
    • 画像は意図的にバンドル化していません

まとめ

photoswipeを導入して、画像クリック時に高解像度の画像ギャラリーが表示されるようにました。

またダイナミックローディングにより画像をクリックしない限り、これまでとほぼ変わらないのでパフォーマンスの劣化も問題ありません。

問題はそこまで高解像度で表示したい画像が今のところない事です。


  1. Single Page Templates | Hugo ↩︎

  2. jQuery file size ↩︎

最終更新:2021/08/31 22:54 (Tue)