Vue.jsとNuxt.jsを使用して、Webページのページ遷移に気持ちいいアニメーションを与えるチュートリアル
Post on:2018年5月22日
Vue.jsとNuxt.jsを使用して、スマホアプリで見かけるようなアニメーションを伴った滑らかなページ遷移をWebページに実装するチュートリアルを紹介します。
Native-Like Animations for Page Transitions on the Web
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
最近見かけたページ遷移のアニメーション
フロントエンドのUIで、最近私が最も刺激を受けたのは、スマホアプリで見かけるような滑らかなページ遷移(トランジション)です。こういったインタラクションを実装するのは難しそうに見えますが、一度実装方法をマスターしてしまえば、さまざまなUIに使用することができます。
ここで紹介する方法で、実装した例を見てみてください。
デモページ
Page Transitions Travelapp -GitHub
VueとNuxtを使ってこれらを作成する方法について説明します。ページのトランジションやアニメーションには多くの可動部分がありますが、心配しないでください。
まずは、このインタラクションを使用する理由から解説します。
使用する理由
近年、WebはiOSやAndroidのネイティブ アプリと比較して「時代遅れである」と批判を受けています。
2つのステータスがあった場合にその間を繋げることは、ユーザーの認知負荷を軽減することができます。例えば、もし要素がいくつかのページで繰り返されていて、でもその要素が少しだけ変更されている場合、ユーザーはページを移動した際にその都度、要素を認識する必要があります。また、変更されたため、要素の関連性を失うかもしれません。しかし、変更された要素を遷移(トランジション)させることで、その要素を簡単に認識することができ、ユーザーの負担が軽減されます。
こうした理由により、トランジションを効果的に使用することで、ユーザーがWeb上ですばやく情報を収集するのに役立つ欠かせないものになります。
喜ばしいことに、トランジションを実装することは実行可能であるということです。
前提となる知識
もしNuxtに慣れていない場合は、下記の記事を一読しておくとよいでしょう。
参考: Simple Server Side Rendering, Routing, and Page Transitions with Nuxt.js
ReactとNext.jsに精通しているなら、Nuxt.jsはVueと同等です。サーバー側のレンダリング、コード分割、そして最も重要なページ遷移のフックを提供します。
トランジションがどのように動作するかを理解するためには、<transition />コンポーネントとCSSアニメーションとトランジションの違いに関する基本的な知識も必要です。Vueのアニメーションについては下記の記事を参考にしてください。
参考: Intro to Vue.js: Animations
また、<transition-group />コンポーネントに関する基本的な知識も必要です。
参考: Creating Vue.js Transitions & Animation
これらの参考記事を読まなくても、実装方法の基本的な要点はここで紹介しますので、心配しないでください。
始めてみよう
まずは、Vue CLIでNuxtの新しいプロジェクトを作成して、始めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# if you haven’t installed vue cli before, do this first, globally: npm install -g @vue/cli # or yarn global add @vue/cli # then vue init nuxt/starter my-transitions-project npm i # or yarn # and npm i vuex node-sass sass-loader # or yarn add vuex node-sass sass-loader |
これで、ページのディレクトリが完成しました。Nuxtはそのディレクトリにある.vueファイルをすべて取得し、自動的にルーティングを設定します。これはかなり便利です。
ここでは、about.vueとusers.vueのいくつかのページを作ります。
フックの設定
前述したように、Nuxtはページ間のトランジションに便利ないくつかのページフックを提供しています。言い換えれば、わたし達はページを出入りするフックを手に入れました。ページ間でフェードが可能なアニメーションを作成したい場合は、フックが既に利用可能であるため、すぐに実装ができます。ページごとに新しいトランジションを指定したり、JavaScriptフックを使用して高度なエフェクトを追加することもできます。
しかし、要素の配置を変えたらどうなるでしょうか?
スマホアプリでは、状態がトランジションするときに必ずしもその要素が残るとは限りません。時には、あるポイントから別のポイントにシームレスに移行し、アプリ全体が非常に流動的であると感じることがあります。
Step 1: Vuexストア
最初にすべきことは、どんなページを持つ必要があるか把握するために、Vuexを使用して状態管理パターンをセットアップします。
Nuxtではこのファイルがstoreディレクトリにあり、index.jsを呼び出すとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import Vuex from 'vuex' const createStore = () => { return new Vuex.Store({ state: { page: 'index' }, mutations: { updatePage(state, pageName) { state.page = pageName } } }) } export default createStore |
両方のページをストアし、ページを更新するためにmutationsを作成します。
Step 2: ミドルウェア
次に、ミドルウェアでは、pages.jsと呼ばれるスクリプトが必要になります。これにより、他のコンポーネントの前にアップデートされているルートにアクセスできるため、非常に効率的です。
1 2 3 4 |
export default function(context) { // go tell the store to update the page context.store.commit('updatePage', context.route.name) } |
また、nuxt.config.jsファイルにミドルウェアを登録する必要があります。
1 2 3 4 5 6 7 |
module.exports = { ... router: { middleware: 'pages' }, ... } |
Step 3: ナビゲーションの登録
layouts/default.vueファイルに行きます。このディレクトリは、さまざまなページ構造に対して異なるレイアウトを設定できます。
今回は新しいレイアウトを作るのではなく、すべてのページで再利用するレイアウトを変更します。テンプレートは最初、下記のようになります。
1 2 3 4 5 |
<template> <div> <nuxt/> </div> </template> |
nuxt/タグは、異なるページにあるテンプレートに何かを挿入します。しかし、すべてのページでnavコンポーネントを再利用するのではなく、ここに追加することができ、すべてのページで一貫して表示されます。
1 2 3 4 5 6 |
<template> <div> <app-navigation /> <nuxt/> </div> </template> |
1 2 3 4 5 6 7 8 9 |
<script> import AppNavigation from '~/components/AppNavigation.vue' export default { components: { AppNavigation } } </script> |
ページが再ルートされる度にレンダリングされないため、とても素晴らしいです。これはすべてのページで一貫しており、ページ遷移のフックに繋がることはできません。しかし、代わりにVuexとミドルウェアの間で構築したもので自分自身を構築できます。
Step 4: ナビゲーションコンポーネントでトランジションを作成
これでナビゲーションを構築することができますが、ここではSVGを使用して、より大きなアプリケーションに実装できる基本機能のデモを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<template> <nav> <h2>Simple Transition Group For Layout: {{ page }}</h2> <!--simple navigation, we use nuxt-link for routing links--> <ul> <nuxt-link exact to="/"><li>index</li></nuxt-link> <nuxt-link to="/about"><li>about</li></nuxt-link> <nuxt-link to="/users"><li>users</li></nuxt-link> </ul> <br> <!--we use the page to update the class with a conditional--> <svg :class="{ 'active' : (page === 'about') }" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 447 442"> <!-- we use the transition group component, we need a g tag because it’s SVG--> <transition-group name="list" tag="g"> <rect class="items rect" ref="rect" key="rect" width="171" height="171"/> <circle class="items circ" key="circ" id="profile" cx="382" cy="203" r="65"/> <g class="items text" id="text" key="text"> <rect x="56" y="225" width="226" height="16"/> <rect x="56" y="252" width="226" height="16"/> <rect x="56" y="280" width="226" height="16"/> </g> <rect class="items footer" key="footer" id="footer" y="423" width="155" height="19" rx="9.5" ry="9.5"/> </transition-group> </svg> </nav> </template> |
1 2 3 4 5 6 7 |
<script> import { mapState } from 'vuex' export default { computed: mapState(['page']) } </script> |
ここではいくつかのことをしています。
スクリプトでは、ストアからページ名を計算値として取り込みます。mapStateはストアから他のものを持ち込むことができ、後で多くのユーザー情報を扱うときに便利です。
テンプレートでは、Nuxtのリンクをルーティングするために使用するnuxt-linkで通常のナビゲーションがあります。また、ページに基づいて条件付きで更新されるclassもあります(これはaboutページのときに.activeに変更されます)。
また、位置を変更するいくつかの要素に<transition-group>コンポーネントを使用しています。このコンポーネントはFLIPを適用するため、魔法のようです。FLIPはWeb上でアニメーション化させる実践的な方法ですが、通常は実装するために多くの計算が必要になるため、これを聞いて非常に興奮しています。
次は、CSSの作業です。
要素のすべてにactiveのフックをどのように配置するのか説明します。そして、変化した場合には、要素にトランジションが適用されるように定義します。下記のCSSで、transformを使用していることに不思議に思うかもしれません。transformを使用する理由は、top/leftのmarginよりもパフォーマンスが優れているためです。
参考: Why Moving Elements With Translate() Is Better Than Pos:abs Top/left
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
.items, .list-move { transition: all 0.4s ease; } .active { fill: #e63946; .rect { transform: translate3d(0, 30px, 0); } .circ { transform: translate3d(30px, 0, 0) scale(0.5); } .text { transform: rotate(90deg) scaleX(0.08) translate3d(-300px, -35px, 0); } .footer { transform: translateX(100px, 0, 0); } } |
ページのトランジションはなしで、動きを示すためのデモを用意しました。
上記のデモで、動きや配置をいろいろ試してみてください。SVGを使用しているのは、少量のコードでレイアウトのコンセプトを伝えられるからです。実際に使用する際に、SVGにする必要はありません。また、アニメーションの代わりにトランジションも使用しています。なぜなら、トランジションは柔軟性があるため、このケースには最適です。もちろん、条件付きの配置に変更することができますが、それでも機能します。
これにより、ページ間に滑らかでスムーズな効果が得られます。ページのコンテンツにもちょっとしたトランジションを与えることができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
.page-enter-active { transition: opacity 0.25s ease-out; } .page-leave-active { transition: opacity 0.25s ease-in; } .page-enter, .page-leave-active { opacity: 0; } |
ページで内部アニメーションを実行できるデモを追加しました。
Simple Page Transitions Demo -GitHub
これは小さなデモでも機能しますが、実際のサイトにあるようなUIに適用してみます。
コンセプトは下記の通りです。
- ページの名前をVuexストアに保存します。
- ミドルウェアはページが変更されたことをストアに知らせるために、mutationsを作成します。
- ページごとに特別なclassを適用し、ページごとにトランジションをネストします。
- ナビゲーションは各ページで一貫していますが、位置が異なり、いくつかのトランジションが適用されます。
- ページのコンテンツには繊細なトランジションがあり、ユーザーイベントに基づいてインタラクションを構築します。
唯一の異なる点は、やや複雑な実装であるということです。
要素に適用されるCSSは、ナビゲーションコンポーネントで同じままです。ブラウザにすべての要素がどの位置にあるかを伝えることができます。要素自体にトランジションが適用されるため、そのトランジションが適用され、ページが変更されるたびに新しい位置に移動します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// animations .place { .follow { transform: translate3d(-215px, -80px, 0); } .profile-photo { transform: translate3d(-20px, -100px, 0) scale(0.75); } .profile-name { transform: translate3d(140px, -125px, 0) scale(0.75); color: white; } .side-icon { transform: translate3d(0, -40px, 0); background: rgba(255, 255, 255, 0.9); } .calendar { opacity: 1; } } |
相対コンテナにFlexbox、CSS Grid、絶対配置を使用して、すべてのデバイスで容易に変換されるようにします。要素の配置とトランジションを宣言できるので、ナビゲーションの変更はCSSを使用しています。
ユーザー主導のイベントのマイクロインタラクションのためには、JavaScriptとGreenSockを使用しています。これは、多くの動きを非常にシームレスに調整し、transform-originをブラウザ間で安定させるためです。このデモを改良したり、これらのアニメーションを作成する方法は何百通りもありますが、実際の状況でいくつかの可能性を示すために分かりやすくしました。
最後に、トランジションを使用する際、ハードウェア アクセラレーターを忘れないでください。ネイティブのような美しいエフェクトを実現できます。
私はあなたが何を作るかワクワクしています!
Webはユーザーの認知負荷を軽減する美しい動き、配置、そしてインタラクションに多くの可能性が感じられます。
sponsors