AstroでViewer.jsを使う

SSGのAstroでつくったサイトに画像ビューワーのViewer.jsを設置したお話です。

前提として、画像はpublicフォルダに置き、frontmatterに画像のパス一覧を書きます。
画像が増えたら、随時frontmatterに追記します。
画像一覧を自動でさらって表示する方法があるのかもしれませんが、俺は知らないので泥臭くやります。（ファイル一覧をJSの動くフロントエンドでさらうことはできないと思っているけど、正しいかはわからない）

`npm install viewerjs`はしておきます。

さて、まずは最終的なコードを提示します。

# 最終的なコード

```jsx:photo.astro---
const paintings = [
  {
    text: "かわいい ねこ",
    list: [
      "/assets/paint/cats/pakutaso/1 (1).jpg",
      "/assets/paint/cats/pakutaso/1 (2).jpg",
      "/assets/paint/cats/pakutaso/1 (3).jpg",
    ],
  },
  {
    text: "かわいい いぬ",
    list: [
      "/assets/paint/dogs/pakutaso/1 (1).jpg",
      "/assets/paint/dogs/pakutaso/1 (2).jpg",
      "/assets/paint/dogs/pakutaso/1 (3).jpg",
    ],
  },
  {
    text: "かわいい はな",
    list: [
      "/assets/paint/flowers/pakutaso/1 (1).jpg",
      "/assets/paint/flowers/pakutaso/1 (2).jpg",
      "/assets/paint/flowers/pakutaso/1 (3).jpg",
    ],
  },
];
---

<!DOCTYPE html>
<html lang="en">
  <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" />
    <title>Document</title>

    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.3/viewer.min.css"
      integrity="sha512-zdX1vpRJc7+VHCUJcExqoI7yuYbSFAbSWxscAoLF0KoUPvMSAK09BaOZ47UFdP4ABSXpevKfcD0MTVxvh0jLHQ=="
      crossorigin="anonymous"
      referrerpolicy9="no-referrer"
    />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.3/viewer.min.js"
      integrity="sha512-f8kZwYACKF8unHuRV7j/5ILZfflRncxHp1f6y/PKuuRpCVgpORNZMne1jrghNzTVlXabUXIg1iJ5PvhuAaau6Q=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    ></script>
  </head>
  <body>
    <div id="gallery">
      <div>
        {
          paintings.map((group, index) => (
            <div class="clickListElement">
              <img src={import.meta.env.BASE_URL + group.list[0]} />
              <p>{group.text}</p>
            </div>
          ))
        }
      </div>
      <div>
        {
          paintings.map((group, index) => (
            <ul style="display:none;" id={"image-" + index}>
              {group.list.map((el) => (
                <li>
                  <img src={import.meta.env.BASE_URL + el} />
                </li>
              ))}
            </ul>
          ))
        }
      </div>
    </div>

    <script>
      import Viewer from "viewerjs";

      Array.from(document.querySelectorAll(".clickListElement")).map(
        (el, index) => {
          const el2 = document.getElementById("image-" + index);
          if (el2) {
            const viewer: Viewer = new Viewer(el2);
            el.addEventListener("click", () => {
              viewer.show();
            });
          }
        }
      );
    </script>
  </body>
</html>
```
# 最終的な表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2846834/d9410029-d5b1-ab16-63b8-e869b06528ee.png)

写真をクリックすると、モーダルで表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2846834/95ecea33-43ab-f0dd-14d3-d750115cefbf.png)



# メモ

## リソース

Viewer.jsのCSSとJavaScriptファイルを読み込みます。

```html
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.3/viewer.min.css"
      integrity="sha512-zdX1vpRJc7+VHCUJcExqoI7yuYbSFAbSWxscAoLF0KoUPvMSAK09BaOZ47UFdP4ABSXpevKfcD0MTVxvh0jLHQ=="
      crossorigin="anonymous"
      referrerpolicy9="no-referrer"
    />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.3/viewer.min.js"
      integrity="sha512-f8kZwYACKF8unHuRV7j/5ILZfflRncxHp1f6y/PKuuRpCVgpORNZMne1jrghNzTVlXabUXIg1iJ5PvhuAaau6Q=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    ></script>
```

## 画像データリスト

`{ text: "タイトル", list: [パス1, パス2, ...] }`というオブジェクトの配列を持ちます。
必要に応じて、これらを追加すると、複数のギャラリーを表示することができます。

```jsx
const paintings = [
  {
    text: "かわいい ねこ",
    list: [
      "/assets/paint/cats/pakutaso/1 (1).jpg",
      "/assets/paint/cats/pakutaso/1 (2).jpg",
      "/assets/paint/cats/pakutaso/1 (3).jpg",
    ],
  },
  {
    text: "かわいい いぬ",
    list: [
      "/assets/paint/dogs/pakutaso/1 (1).jpg",
      "/assets/paint/dogs/pakutaso/1 (2).jpg",
      "/assets/paint/dogs/pakutaso/1 (3).jpg",
    ],
  },
```

## クリック用の画像表示

ビューワーを開くためのクリック用の画像を表示します。
画像のパスは`astro.config.mjs`で設定した`base`オプションを参照して、適切に画像表示されるようにしました。

```jsx
  <div>
    {
      paintings.map((group) => (
        <div class="clickListElement">
          <img src={import.meta.env.BASE_URL + group.list[0]} />
          <p>{group.text}</p>
        </div>
      ))
    }
  </div>
```

## ビューワー用の画像リスト表示（ただし、非表示設定）

ビューワーで表示する画像のリストを出力します。
ただし、画像自体はビューワーで表示され、リスト自体は表示される必要がないので、`display:none;`しておきます。

また、ビューワーとクリック用の画像を関連させるため、`id`として`image-{index}`を設定します。

画像パスは前述と一緒です

```jsx
  <div>
    {
      paintings.map((group, index) => (
        <ul style="display:none;" id={"image-" + index}>
          {group.list.map((el) => (
            <li>
              <img src={import.meta.env.BASE_URL + el} />
            </li>
          ))}
        </ul>
      ))
    }
  </div>
```

# Viewer.jsの適用

肝心のViewer.jsを適用します。

クリック用の画像（.clickListElement）をすべて取得し、`index`番号で対応する画像リストを選択、それにViewerを適用します。

さらに、クリック用の画像（.clickListElement）にonclickを設定し、Viewer.jsのshowファンクションを実行します。

```js
import Viewer from "viewerjs";

Array.from(document.querySelectorAll(".clickListElement")).map(
(el, index) => {
  const el2 = document.getElementById("image-" + index);
  if (el2) {
    const viewer: Viewer = new Viewer(el2);
    el.addEventListener("click", () => {
      viewer.show();
    });
  }
}
);
```

# 悩んだところ

frontmatterで提議しているpaintings変数にscriptタグ内からアクセスできないので、少しつまずきました。

結論として、querySelectorAllなどで出力済みのHTML要素から必要なデータを読みとることで解決しました。


# Thanks
ぱくたそ様から画像を借用しました。
[https://www.pakutaso.com/](https://www.pakutaso.com/)