Vue.jsでギャラリーを実装してみる

こんにちは、[どら](https://twitter.com/d0ra1998)です。
Advent Calendar、大遅刻してしまって本当にすみません…😥

今回は、以前制作した中で実装したiOS風のギャラリーを紹介したいと思います。

## この記事で紹介するもの

今回実装するのは、下に横スクロールの画像リストがあり、上に大きく画像が表示されるiOS風(?)のギャラリーです。
[サンプルページ](https://vue-gallery-sample.netlify.com/)
<img alt="完成したギャラリー" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51163/af43bcfc-99ce-a66a-eece-60bd8d8b13b0.png" width="800px">

なお、全体のソースコードは以下のリポジトリにて公開しています。
<a href="https://github.com/dora1998/vue-gallery-sample"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/dora1998/vue-gallery-sample.png" width="460px"></a>

## 実装する上でのポイント

### 画像が絡むAPIを使いたいサンプルにはpixabayが便利

今回ギャラリーを作るにあたっては、[pixabayのAPI](https://pixabay.com/api/docs/)を使用させていただいています。
Googleアカウントなどで新規登録をすれば、すぐにAPIキーがドキュメントに表示されるので大変ラクチンです。

複雑な認証等も必要ないので、すぐに使い始めることができます。

### overflowさせるには、`min-width`を使う

下の横スクロールする画像は、flexboxを用いて並べられています。
この時、普通に`width`や`flex-basis`を使ってしまうと、overflowしないように縮められてしまい、うまくいきません。

これは、`min-width`で幅を指定してあげれば回避することができます。

参考）[横スクロールの実装でflexboxを使ったのでそのメモ。](https://qiita.com/_katomasa/items/b131b3cf0b78ce3933d5)

### 選択されている画像が中央に来るようにする

`http://localhost:3000/?id=3`といったように、現在選択している画像をアドレス内に含め、そのアドレスを直接開いた時に該当画像を選択した状態になるように実装しています。

それにあたり、下部に表示する横スクロールリストも選択画像が中央に来るようにしています。これには、DOM操作が必要なため、[`ref`属性](https://jp.vuejs.org/v2/api/#ref)を用いる必要があります。

```vue
<template>
  <div ref="container" :class="`${$options.name}__root`">
    <div
      v-for="(image, i) in images"
      :ref="`img-${i}`"
      ...
    >
      <v-lazy-image :src="image.previewURL" />
    </div>
  </div>
</template>
```

このようにrefを指定することで、

```javascript
const imgRefs = this.$refs[`img-${idx}`][0]
```

とすると、DOMを直接取ることができます。

最終的には、以下のように実装することで所望の画像を中央に持ってくることができました。

```typescript
getImagePos(idx: number) {
  const imgRefs = this.$refs[`img-${idx}`]
  if (Array.isArray(imgRefs) && imgRefs.length > 0) {
    const imgRef = imgRefs[0] as Element
    return imgRef.getBoundingClientRect()
  } else {
    return null
  }
},
moveToSelectedImg(idx: number) {
  // 画像のdivの位置を取得
  const selectedPos = this.getImagePos(idx)
  if (selectedPos === null) return

  // 横スクロールリストの箱自体の位置を取得
  const containerRef = this.$refs.container as Element
  const containerRect = containerRef.getBoundingClientRect()

  // リスト内での画像中央位置を計算
  // * leftはページ全体での座標なので、コンテナ内での相対座標にするためにコンテナの左端座標を引く
  const centerPos =
    selectedPos.left + selectedPos.width / 2 - containerRect.left
  // セットすべきscrollLeftを計算
  const scrollLeftPos = centerPos - containerRect.width / 2

  containerRef.scrollLeft = scrollLeftPos
}

// 移動したいタイミングで下を呼び出す
this.moveToSelectedImg(this.selected)
```

ただ、$refsで操作ができるのはDOMが構築された後なので、呼び出すタイミングを考えないとnullになりがちです。
今回は、imagesが渡された後に若干setTimeoutで待ちを入れて操作しています（本当は使わずになんとかしたい）

### APIキーを環境変数に入れる

PixabyのAPIを利用するには、ログイン後に取得するAPIキーが必要です。ただ、あまりこのようなキーをリポジトリに含めて管理するのはあまり好ましくないので、環境変数を参照するようにしました。

Nuxt.jsには[@nuxtjs/dotenv](https://github.com/nuxt-community/dotenv-module)というパッケージがあり、こちらを利用することで、サーバーサイドでよくするように`.env`ファイルから読み込むことができます。

導入は、特に特別な設定をしなければ、以下の設定を行うのみでOKです。

```shell_session
$ yarn add --dev @nuxtjs/dotenv # or npm install --save-dev @nuxtjs/dotenv
```

```javascript:nuxt.config.js
export default {
  buildModules: [
    '@nuxtjs/dotenv',
  ]
}
```

`.env`ファイルにはこのように記述します。

```:.env
PIXABAY_APIKEY=XXXXXX
```

Netlifyなど本番環境で環境変数として注入する場合は、`nuxt.config.js`にさらに以下のように書く必要があります。

```javascript:nuxt.config.js
export default {
  env: {
    PIXABAY_APIKEY: process.env.PIXABAY_APIKEY
  }
}

```


## まとめ

以前、とあるプロジェクトの中でこのようなギャラリーを実装して個人的に満足していたので今回記事にしてみました。
データバインディングを超えて、より複雑なことをしたい場合にはrefsをうまく活用してみると良いですね。