🅰️

Angular で GLightbox を使う

2023/05/06に公開

前書き

もともと Fancybox の少し古いバージョンを使用していたのですが、jQuery への依存を無くすために代わりに使えるものを探していました。
いくつか試した所、 GLightbox というのが良かったので、ちょっとした TIPS と共に基本的な使い方を紹介したいと思います。
なお、 GLightbox は動画の再生などもできる優秀なライブラリですが、本記事では画像の表示のみを取り扱います。

バージョン

Angular: 15.2.0,
glightbox: 3.2.0,

動作確認用

https://github.com/negibouze/angular-glightbox

本題

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.jsontypeRoots を追加します。

"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 あたりで初期化します(constructorngOnInit だと画像の用意ができていないのでうまく動きません)。

// xxx.component.ts
import { AfterViewInit, Component } from '@angular/core';
import GLightbox from 'glightbox';

〜中略〜

ngAfterViewInit(): void {
  GLightbox({});
}

5. 複数のギャラリーに対応する

  1. の状態だと .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 画像に対応する

  1. までで大体終わりなのですが、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