はじめに
スマホでサムネイル画像をタップしたら、拡大表示はできるけどピンチズームができない。スワイプで次の画像に切り替えたいのに、画面端の戻る操作と干渉して意図しない画面遷移が起きる。画像ギャラリーを自前で実装したことがある方なら、一度はこうしたタッチ操作周りの作り込みに苦戦した経験があるのではないでしょうか。
PhotoSwipeは、こうしたモバイル特有の操作性の問題を最初から解決した状態で提供してくれる画像ギャラリー・ライトボックスライブラリです。ピンチズーム、スワイプでの画像切り替え、ドラッグでのクローズといったジェスチャーが標準で組み込まれており、フレームワークにも依存しないため、React・Vueはもちろん素のHTMLサイトにもそのまま導入できます。
とはいえ、説明を読むより実際に動かした方が早いと思います。まずは触ってみたい方はこちらからどうぞ。
PhotoSwipeとは
PhotoSwipeは、Dmytro Semenov氏が開発しているJavaScript製の画像ギャラリー・ライトボックスライブラリです。2011年から続く歴史あるプロジェクトで、v5では内部実装がモジュール化され、必要な機能だけを読み込める構成になりました。GitHubで25,000以上のスターを獲得しており、モバイル対応の画像ビューアとして長く使われ続けています。
主な特徴
- タッチジェスチャー標準対応 - ピンチズーム、スワイプでの画像送り、下方向ドラッグでのクローズが最初から実装済みです
- フレームワーク非依存 - コア部分は素のJavaScriptで書かれており、React・Vue・Angularのどれとも組み合わせられます
- 軽量・モジュール構成 -
PhotoSwipeLightboxとPhotoSwipe本体(コアビューア)が分離されており、必要なタイミングで遅延読み込みできます - 柔軟なカスタムUI -
uiRegisterイベントを使って、キャプションや独自ボタンをUIに追加できます - DOMなしでも動く -
dataSourceオプションに画像情報の配列を渡せば、あらかじめギャラリー用のHTMLを用意しなくても起動できます
インストール
npm、yarn、pnpmのいずれでも導入できます。
npm install photoswipe
yarn add photoswipe
pnpm add photoswipe
PhotoSwipeのサンプルを動かす
PhotoSwipeLightboxにギャラリーのセレクタと、リンク先の画像URLを渡すだけで、サムネイル一覧からタッチ操作対応のライトボックスを開けます。ポイントは<a>タグにdata-pswp-width・data-pswp-height属性で元画像のサイズを指定しておくことです。これによりPhotoSwipeは画像を読み込む前に表示領域を確保でき、開いた瞬間のガタつきを防げます。
import PhotoSwipeLightbox from 'photoswipe/lightbox'
import PhotoSwipe from 'photoswipe'
import 'photoswipe/style.css'
const lightbox = new PhotoSwipeLightbox({
gallery: '#gallery', // ギャラリー全体を囲む要素のセレクタ
children: 'a', // ギャラリー内でクリック対象にする子要素
pswpModule: PhotoSwipe, // ビューア本体(コア)
})
lightbox.init()
<div id="gallery">
<a href="large-1.jpg" data-pswp-width="1200" data-pswp-height="800">
<img src="thumb-1.jpg" alt="Photo 1" />
</a>
</div>
下のサンプルはサムネイルをクリックすると実際にライトボックスが開きます。画像は外部サーバーに依存させず、SVGをその場で生成して表示しているので、そのまま編集して試せます。サムネイルをタップ・クリックしたあと、スマホであればピンチズームやスワイプでの画像送りも試してみてください。
children: 'a'をchildren: 'a.gallery-item'のようにクラス指定へ変えれば、同じ#gallery内に別用途のリンクを混在させても、ギャラリー対象だけを絞り込めます。またdata-pswp-widthとdata-pswp-heightの値を実際の画像サイズと違うものにすると、開いた瞬間に表示がずれるので、この2つの属性は正確に指定するのがポイントです。
基本的な使い方
最小構成では、ギャラリー用のHTMLを用意し、PhotoSwipeLightboxを初期化するだけで動きます。
import PhotoSwipeLightbox from 'photoswipe/lightbox'
import PhotoSwipe from 'photoswipe'
import 'photoswipe/style.css'
const lightbox = new PhotoSwipeLightbox({
gallery: '#my-gallery',
children: 'a',
pswpModule: PhotoSwipe,
})
lightbox.init()
<div id="my-gallery">
<a href="images/photo-1-large.jpg" data-pswp-width="1600" data-pswp-height="1200">
<img src="images/photo-1-thumb.jpg" alt="" />
</a>
<a href="images/photo-2-large.jpg" data-pswp-width="1600" data-pswp-height="1067">
<img src="images/photo-2-thumb.jpg" alt="" />
</a>
</div>
pswpModuleには、上記のようにビューア本体をあらかじめimportして渡す方法のほかに、pswpModule: () => import('photoswipe')のように動的importの関数を渡す方法もあります。後者にすると、ライトボックスが実際に開かれるまでビューア本体のコードが読み込まれないため、初期表示を軽くしたい場合に有効です。
実践的なユースケース
dataSourceとボタンで画像を直接開く
ギャラリー用のHTMLをあらかじめDOMに用意できないケース、たとえば検索結果やAPIから取得した画像をボタン操作で開きたい場合は、dataSourceオプションに画像情報の配列を渡す方法が使えます。lightbox.loadAndOpen(index)を呼び出すだけで、指定したインデックスの画像がライトボックスで開きます。
const lightbox = new PhotoSwipeLightbox({
dataSource: [
{ src: 'img1.jpg', width: 1200, height: 900, alt: '画像1' },
{ src: 'img2.jpg', width: 1200, height: 900, alt: '画像2' },
],
pswpModule: PhotoSwipe,
})
lightbox.init()
button.addEventListener('click', () => {
lightbox.loadAndOpen(0) // 0番目の画像を開く
})
以下のサンプルでは、3つのボタンそれぞれにloadAndOpenで開くインデックスを紐づけています。ボタンを押すごとに異なる画像が開くのを確認してください。
dataSourceの配列は固定値である必要はなく、APIレスポンスをそのままマッピングして渡すこともできます。検索結果一覧やタグ絞り込みのように、表示する画像がJavaScript側の状態で決まるUIと相性が良い書き方です。
キャプションを表示するカスタムUI登録
商品写真や観光地の紹介など、画像に説明文を添えたい場面は多くあります。PhotoSwipeはlightbox.on('uiRegister', ...)で独自のUI要素を追加できるため、pswp.ui.registerElementを使ってキャプション表示エリアを組み込めます。
lightbox.on('uiRegister', () => {
lightbox.pswp.ui.registerElement({
name: 'caption',
order: 9,
isButton: false,
appendTo: 'root',
onInit: (el) => {
lightbox.pswp.on('change', () => {
const img = lightbox.pswp.currSlide.data.element?.querySelector('img')
el.textContent = img?.getAttribute('alt') || ''
})
},
})
})
img要素のalt属性からキャプション文字列を取り出し、スライド切り替え時(changeイベント)に表示を更新しています。以下のサンプルで、画像を切り替えるたびにキャプションが変わることを確認してください。
appendTo: 'root'を'bar'に変えると、キャプションが上部のツールバー内に表示されるようになります。ボタンを追加したい場合はisButton: trueにしてonClickを指定すれば、独自のUIコントロールとしても登録できます。
ズーム・アニメーションのオプションを切り替える
PhotoSwipeはwheelToZoom(マウスホイールでのズーム可否)やshowHideAnimationType(開閉アニメーションの種類)など、動作を細かく調整するオプションを豊富に持っています。設定値を変えて挙動を見比べたいときは、初期化オプションとしてまとめて渡すだけです。
const lightbox = new PhotoSwipeLightbox({
dataSource: [photo],
wheelToZoom: true, // ホイールでズームできるようにする
showHideAnimationType: 'fade', // 'zoom' | 'fade' | 'none'
pswpModule: PhotoSwipe,
})
以下のサンプルでは、チェックボックスとセレクトボックスでwheelToZoomとshowHideAnimationTypeを切り替えてから「画像を開く」を押せます。設定を変えてから開き直すと、開閉時のアニメーションの違いが分かります。
showHideAnimationTypeを'none'にすると、開閉時のズームアニメーションが省略され、一覧の表示件数が多いギャラリーでも動作を軽く保てます。逆に演出を重視したいトップページのギャラリーなどでは'zoom'のままにしておくと、サムネイルから拡大されるような自然な見え方になります。
まとめ
PhotoSwipeを使うと、ピンチズームやスワイプ操作に対応したモバイルフレンドリーな画像ギャラリーを、ジェスチャー周りの実装に悩まされることなく組み込めます。galleryとchildrenを指定するDOMベースの基本形に加えて、dataSourceとloadAndOpenによるプログラム的な起動、uiRegisterによるキャプションやカスタムUIの追加、wheelToZoomやshowHideAnimationTypeといった細かなオプション調整まで、シンプルな構成のわりに対応できる幅は広いライブラリです。
まずは今回のサンプルをベースに、実際の画像URLに差し替えて手元のプロジェクトに組み込んでみてください。
