Blog

【Nuxt.js】読み込むコンポーネントを動的に変更し、下層間のアニメーションを設定する

このポートフォリオサイトの話です。今まで「Collage」として公開していたページを、「Gallery」に変更しました。

それまでは「Collage」というページコンポーネントでしたが、これをgallery内の一つにするという作業が発生。
そこでちょっと悩んだので備忘録です。

先に書いておきますが、これが最善策かどうかはわかりません。
いろいろ悩んだ結果、こうなったという記録を残しておこうと思います。

前提(やりたいこと)

galleryの下層に、collageとsakuhitoを作る。

  • gallery/collage/
  • gallery/sakuhito/

ギャラリーの間を移動する場合は、ギャラリー用のアニメーションを入れる。

作り方の案

1、普通にページコンポーネントで作ろうかと考える

最初は、特に何も考えず、pagesフォルダにそれぞれ「gallery/collage.vue」と「gallery/sakuhito.vue」を作ってみました。

これでも問題ないのですが、アニメーションの部分で微妙でした。

例えばAboutのページからGalleryのページに移動するときは、他のページ移動のアニメーションと一緒でもいいのですが、
Gallery間のページ移動も同じアニメーションになってしまいます。

Galleryのページは、Galleryのナビがあります。
このナビも一度消えて、また同じところに出現するというアニメーションになってしまいました。

できれば、Gallery共通のレイアウトテンプレートを作って、コンポーネントを切り替えて表示させ、他のページ移動アニメーションとは別の、ギャラリー間移動のアニメーションをつけたいです。

そこで、Galleryの共通レイアウトテンプレートをどう作るか?と考えた時に、一案としてlayoutのvueファイルを作るという方法があります。

2、gallery専用のlayoutファイルを作ろうかと考える

ページコンポーネントは、layout/default.vueがトップレベルのテンプレートとなっていますが、ここに
layout/gallery.vue というファイルを作ってカスタムレイアウトを作れば、ギャラリーのページ共通のテンプレートができます。

各ページで、レイアウトを指定する必要があります。

export default {
  layout: 'gallery'
}

…しかし、これでも同じで、アニメーションが微妙です。
カスタムレイアウトを作るということは、<Nuxt />タグが他のページとは別のテンプレートに記載されるわけなので、
もう「.page-leave-active」などのページコンポーネント間のアニメーションも効かなくなってしまいます。

3、transitionキーでアニメーションを出し分けようと考える

layoutはすべて「layout/default.vue」で統一し、アニメーションはtransitionキーで設定しようと考えました。

Gallery共通のレイアウトは、default.vueの中で分岐し、Galleryのページであればナビを追加するという方法にすれば、Galleryのナビが一度消えることもありません。

export default {
  transition:'galleryAnime',
  〜省略〜
}

これが最善だと思ったのですが、挙動が少し思っていたのと違いました。

例えば、

about.vue に transition:'aboutAnime'
gallery/collage.vue に transition: 'galleryAnime',

を設定したとします。

希望するアニメーションは、

aboutのページからGalleryへ行く時 → 'aboutAnime'
Galleryのページからaboutへ行く時 → 'aboutAnime'
Gallery間の移動 → 'galleryAnime'

なのですが、このtransitionキーを使った方法だと、

aboutのページからGalleryへ行く時 → 'galleryAnime'
Galleryのページからaboutへ行く時 → 'aboutAnime'
Gallery間の移動 → 'galleryAnime'

となり、外からGalleryへ入ってきた時のアニメーションにgallery用のアニメーションが適用されてしまいました。
・・・考えてみれば、Nuxt的にはこれが自然なアニメーションなのかもしれません。

しかし、下層同士で移動するためのアニメーションが今回はほしいので、そこを汲んでくれる設定方法はないかと探しましたが…
見つけられませんでした。

4、ページコンポーネントではなく、単一ファイルコンポーネントにする

pagesにcollage.vueやsakuhito.vueを格納するのではなく、これらを単一ファイルコンポーネントにして、
テンプレートを動的テンプレートで作るという方法にしました。

そもそも、このgalleryは、以下の特徴があります。

  • 増える可能性もあるが、増やす人は自分だけである
  • 1ページ1ページ、HTMLとCSSで作り込む(CMSではない)

なので、最初はページコンポーネントとして静的に作って存在させようとしていました。
アニメーションがなければ特に違和感なくそれでいけたと思います。

しかし、コンテンツが増える可能性があるということは、動的にしても論理的にはおかしくはないと思い、
ページの固有の部分は「component/Collage.vue」などに格納、
ページコンポーネントは「_id.vue」と動的に作成することにしました。

こうすれば、_id.vueがgalleryの共通部分を含むテンプレートになり、アニメーションも特有のものを作ることができます。

単一ファイルコンポーネントを動的に読み込む

該当のページに合ったコンポーネントをよみこませるのは、「is」をbindすることで可能です。

<transition name="galleryAnime" mode="out-in">
  <component :is="galleryComponent" />
</transition>

compontタグは、:is(v-bind:is) を用いてコンポーネントを渡すことで、指定したコンポーネントを挿入することができます。
galleryComponentの部分は次のいずれかです。

  • 登録されたコンポーネントの名前
  • コンポーネントのオプションオブジェクト

今回は、登録されたコンポーネントの名前を渡します。
dataにgalleryComponentを作って、collageやsakuhitoなどが入るようにします。

import Collage from '@/components/gallery/Collage.vue';
import Sakuhito from '@/components/gallery/Sakuhito.vue';

export default {
  components:{
    Collage,
    Sakuhito,
  },
  data() {
    return {
      galleryComponent: "",
    };
  },
  created(){
    const param = this.$route.params.id;
    this.galleryComponent = param;
  },
}

createdには、ページ読み込み時にルートからページネームを取得し、galleryComponentに格納する処理をいれておきました。

ギャラリーのナビをクリックすると、galleryComponentのデータがかわるようにする

ナビをクリックすると、dataが差し代わるようなメソッドを書きます。

<template>
  〜省略〜
  <nav class="galleryNavi">
    <ul>
      <li @click="movePage('collage')">cumak</li>
      <li @click="movePage('sakuhito')">咲仁</li>
    </ul>
  </nav>
  〜省略〜
</template>

export default {
  〜省略〜
  methods:{
    movePage(pagename){
      this.galleryComponent = pagename;
    }
  }
  〜省略〜
}

コンポーネントが差し代わってもURLが変わらないので、パスを変更する処理を加える

上記の状態でナビをクリックすると、コンポーネントは差し代わるのですが、URLが変更されません。

gallery/collage/
から
gallery/sakuhito/
にかわってほしいのですが、単一ファイルコンポーネントに変更があっただけでは、ページが変更されたとは認識されません。

かといってナビ部分に<NuxtLink>を使うと、ページ移動が起こるのでアニメーションが他のものと同じになってしまいます。

そこで、replaceStateでURLを変更します。

  methods:{
    movePage(pagename){
      this.galleryComponent = pagename;
      window.history.replaceState(null,null,"/gallery/"+pagename+"/");
    }
  }

ちなみに、replaceStateではなくpushStateでもよいのですが、pushStateにするとブラウザバックで戻ろうとした時にURLが変わるだけでページが切り替わりません。

pushStateは履歴を保存するので、例えば
about/ → gallery/collage/ → gallery/sakuhito/
と進んだ時に、ブラウザのバックボタンをでcollageのページに戻ろうとすると、表示はそのままでURLだけが戻ります。

replaceStateにすると新しく履歴エントリを作成する代わりに現在の履歴エントリを修正するということになり、上記の例でブラウザのバックボタンを押すと、sakuhitoからaboutにとんで戻ります。

表示もURLも戻ってくれるのが一番いいですが、それはできなさそう(できるかもしれないですが)で、まだこちらの方が良いと思ったので、replaceStateでいくことにしました。

generate設定でルーティングを知らせる

このサイトはSSG(Static Site Generator)で制作されています。
それゆえに静的ファイル生成時に、ルーティングを知らせる必要がありますが、Nuxt v2.13からは、nuxt generateを実行した時、クローラがリンクタグをクロールしてルートを自動で生成してくれます。

通常なら特に何もしなくても、クローラが存在するページを探してくれるのですが、今回はページコンポーネントをやめて、動的ルーティング+単一ファイルコンポーネントにしたため、Nuxtにルーティングを知らせる必要がでてきました。

具体的には、nuxt.config.jsで generate.routes プロパティに動的なルートの配列を設定する必要があります。

nuxt.config.js のgenerateに下記のようにコンポーネントを列挙すれば、
npn run generateで生成してくれるようになります。

<公式の例>

export default {
  generate: {
    routes: ['/users/1', '/users/2', '/users/3']
  }
}

ギャラリーのパスを列挙した配列を手動で作り、それを指定することにしました。

// ギャラリーページでページ生成が必要なもの
const galleryPageArr = ['/gallery/sakuhito/'];
export default {
  generate: {
    routes: galleryPageArr
  }
}

アニメーションのSCSS

SCSS(CSS)を適用すれば、ギャラリー間でアニメーションが実行されます。

.galleryAnime{
  &-leave-active,
  &-enter-active{
    transition:all .4s;
  }
  &-leave-to {
    transform:translateX(100px);
    opacity: 0;
  }
  &-enter{
    transform:translateX(-100px);
    opacity: 0;
  }
  &-enter-to{
    transform:translateX(0);
    opacity: 1;
  }
}

まだ不満な点

やりたいことは実現できましたが、なんだかやり残したことがあるような気もしています。
本来ならば、動的ルーティングではなく、各ページをちゃんと作るのだから、ページコンポーネントとして認識させたかったところです。
ページコンポーネントとして作らなかったばっかりに、

  • pagesフォルダの恩恵を受けられない(動的テンプレートにコンポーネントをいちいちimportする必要がある)
  • URLだけを変更する処理が必要
  • generate時にルーティングが自動認識されない

といった問題や追加作業が発生しました。

しかし、なぜページコンポーネントにしなかったのかというと
「ギャラリー間のアニメーションはその他のページとは別にしたい」からです。
下層のページ間の移動は、それ専用のanimationが欲しい…それだけの理由です。

絶対やり方があるような気がしてならないのですが、(transitionキーがとにかく惜しい)
もうちょっと経験を積む必要があると感じました。

おわりに

昔のホームページには、「リンク」というページがあることがほとんどで、リンク集みたいなページを作って知り合いのサイトと相互リンクしあうみたいなのがありましたよね。
時はたち、SEOの考え方もいろいろ変わって、最近はほとんどそういったものは見かけません。(被リンクがSEOに有効なのは変わりませんが)

今回Galleryのページを作ろうと思ったのは、昔からの知り合いで素敵な絵を描く人がいて、
WEBサイトの素材としてもすごくいいし、何より世に紹介したい。

自分はデザイナーではなく、こういった素材を作ることができる人をリスペクトしています。

今回「そういえば、昔のWEBサイトって「リンク集」みたいなんよく見かけたなあ…」と思い出し、イマドキ風に、いい感じに見せられるページを作れないだろうか
と考えた末に、Galleryというページを作ってみました。

アーティストさんに恵まれたら、これから増やしていこうと思います。
(増えなかったらすみません)

おすすめの記事 recommend blog

新着 new blog

github