Angular で GLightbox を使う
前書き
もともと Fancybox の少し古いバージョンを使用していたのですが、jQuery への依存を無くすために代わりに使えるものを探していました。
いくつか試した所、 GLightbox というのが良かったので、ちょっとした TIPS と共に基本的な使い方を紹介したいと思います。
なお、 GLightbox は動画の再生などもできる優秀なライブラリですが、本記事では画像の表示のみを取り扱います。
バージョン
Angular: 15.2.0,
glightbox: 3.2.0,
動作確認用
本題
1. インストール
省略します。
2. CSS ファイルを import する
styles.css
とかに追加します。
@import '~glightbox/dist/css/glightbox.css';
3. 型定義ファイルを追加する
残念ながら、2023年5月5日時点での最新版である v3.2.0 では TypeScript への対応ができていません(feat: glightbox types で進めてくれているので、近いうちに対応されると思います)。
そのため、自分で型定義ファイルを追加します。
d.ts
ファイルを作る
適当な場所に d.ts
を追加します(本記事では src/types/glightbox.d.ts
)。
型を定義する
こんな感じのを定義します。
実際には自分が必要なものだけ定義すれば良いと思います。
例えば、画像にしか使わないのであれば、 player 系は必要ありません。
// glightbox.d.ts
declare module 'glightbox' {
export type GLightboxOptions = {
selector?: string;
elements?: [];
skin?: string;
openEffect?: string;
closeEffect?: string;
slideEffect?: string;
moreText?: string;
moreLength?: number;
closeButton?: boolean;
touchNavigation?: boolean;
touchFollowAxis?: boolean;
keyboardNavigation?: boolean;
closeOnOutsideClick?: boolean;
startAt?: number;
width?: number;
height?: number;
videosWidth?: number;
descPosition?: string;
loop?: boolean;
zoomable?: boolean;
draggable?: boolean;
dragToleranceX?: number;
dragToleranceY?: number;
dragAutoSnap?: boolean;
preload?: boolean;
svg?: Record<string, any>;
cssEfects?: Record<string, any>;
lightboxHTML?: string;
slideHTML?: string;
autoplayVideos?: boolean;
autofocusVideos?: boolean;
plyr?: Record<string, any>;
}
export type GlightboxSlideConfig = Record<string, any>
export type GlightboxEventType = 'open' | 'close' | 'slide_before_change' | 'slide_changed' | 'slide_before_load' | 'slide_after_load' | 'slide_inserted' | 'slide_removed';
// TODO:
// This is probably the same as Plyr, but it is not needed in this repository and will be omitted.
// https://github.com/sampotts/plyr
export type GlightboxPlayer = Record<string, any>;
export type Glightbox = {
open: (node?: any) => void;
openAt: (index: number) => void;
close: () => void;
reload: () => void;
destroy: () => void;
prevSlide: () => void;
nextSlide: () => void;
goToSlide: (index: number) => void;
insertSlide: (config: GlightboxSlideConfig, index: number) => void;
removeSlide: (index: number) => void;
getActiveSlide: () => Element;
getActiveSlideIndex: () => number;
slidePlayerPlay: (index: number) => void;
slidePlayerPause: (index: number) => void;
getSlidePlayerInstance: (nodeOrindex: Node | number) => boolean | Record<string, any>;
getAllPlayers: () => GlightboxPlayer[];
setElements: (elements: any[]) => void;
on: (type: GlightboxEventType, fn: () => void, once?: boolean) => void;
once: (type: GlightboxEventType, fn: () => void) => void;
}
export default function (options: GLightboxOptions): Glightbox;
}
型定義ファイルの場所を追加する
tsconfig.json
に typeRoots
を追加します。
"compilerOptions": {
...,
"typeRoots": [
"types",
],
},
これでこの記事の要点はほぼ終わったようなものです。
4. とりあえず使ってみる
GLightbox のデフォルトのセレクターは .glightbox
なので、対象のタグに追加します。
// html
<a
*ngFor="let image of images"
class="glightbox" <= これ
[href]="image"
>
<img class="image" [src]="image" alt="">
</a>
TS ファイルでは glightbox
を import して、ngAfterViewInit
あたりで初期化します(constructor
や ngOnInit
だと画像の用意ができていないのでうまく動きません)。
// xxx.component.ts
import { AfterViewInit, Component } from '@angular/core';
import GLightbox from 'glightbox';
〜中略〜
ngAfterViewInit(): void {
GLightbox({});
}
5. 複数のギャラリーに対応する
- の状態だと
.glightbox
がついた要素全てを対象に一つのギャラリーが作られます。
そのため、一覧の行ごとにグルーピングしたいといったケースには合いません。
そこで、複数のギャラリーを持てるように修正します。
対応方法1. data-gallery
属性を使う
最も簡単なのは対象要素に data-gallery
属性を追加することです。
<a
*ngFor="let image of gallery"
class="glightbox"
[href]="image"
data-gallery="gallery-name" <= これ
>
<img class="image" [src]="image" alt="">
</a>
これだけで、data-gallery
の値によってグルーピングされます。
しかし、この方法の場合、各値がユニークになるようにどこかで管理をしなくてはならず、大量のギャラリーを扱う必要がある場合は少し面倒です。
dh対応方法2. GLightbox.setElements
を使う
GLightbox
では setElements
で対象の要素を変更できます。
これを使うことで、data-gallery
を使わずにグルーピングをすることができます。
※実装の詳細はリポジトリをご参照ください。
// gallery.component.ts より抜粋
import GLightbox, { Glightbox as lightbox } from 'glightbox';
export class GalleryComponent implements OnInit {
@ViewChildren(ImageComponent) imageComponents!: QueryList<ImageComponent>;
private lightbox: lightbox | null = null;
ngOnInit(): void {
this.lightbox = GLightbox({}); <= 初期化だけしておく
}
private registerOnLightbox(): void {
if (!this.imageComponents) {
return
}
this.imageElements = this.imageComponents.map(com => com.imageElement);
this.imageElements.forEach(element => {
element.removeEventListener('click', this.handleImageClickBound)
element.addEventListener('click', this.handleImageClickBound)
})
const elements = this.imageElements.map(image => ({
href: image.src, <= とりあえず `href` だけあれば表示は可能
}))
this.lightbox?.setElements(elements); <= スライド表示したい要素を設定する
}
}
こちらの方法の場合、子要素側には GLightbox
に関する情報を持たせる必要がありません。
また、サムネイルとスライドで同じ画像を使うのであれば、a タグも不要です(ちゃんとサムネイル画像を用意している場合は a タグを使うか、image.component.ts から画像の URL を取得できるようにすると良いと思います)。
// image.component.html
<img class="image" [src]="imageUrl" alt="" (load)="loaded.emit()" #image>
6. base64 画像に対応する
- までで大体終わりなのですが、base64 画像が対象になる場合は一手間必要になります(と言ってもオプションを一つ追加するだけです)。
GLightbox
では下記のように、URL の値からタイプの判定をしてくれるのですが、base64 画像の場合はimage
の条件に合わないためexternal
として扱われます(ちなみに、その場合iframe
で表示されます)。
sourceType(url) {
let origin = url;
url = url.toLowerCase();
if (url.match(/\.(jpeg|jpg|jpe|gif|png|apn|webp|avif|svg)/) !== null) {
return 'image';
}
}
そこで、 setElements
する際に type
を指定する必要があります。
const elements = this.imageElements.map(image => ({
href: image.src,
type: 'image', <= これを追加
}))
これで、 base64 画像も image
として扱われます。
指定しておくと判別処理をスキップできるので、あらかじめ分かっているのであれば base64 画像の有無に関わらず指定しておいて良いんじゃないかなと思います。
終わりに
ここが違うよ、こうすればもっと簡単にできるよ、などありましたら、コメントをいただけると、とてもありがたいです。
Discussion