Web技術の発展に伴い、近年のWeb制作はより複雑で面白いものになってきました。
そこで、今回はSPA(シングルページアプリケーション)で作るページ遷移のデモを制作しましたので、ご紹介しようと思います。
Nuxt.jsについて
SPAを構築するにあたり、今回はJavaScriptライブラリ(以下jsライブラリ)のフレームワークを採用することにしました。jsライブラリは有名なものだと「React」や「Vue」などが該当しますが、今回は「Vue」のフレームワークである「Nuxt.js」を使用します。
導入方法
Nuxt.jsの導入は公式サイトにも書いてある「create-nuxt-app」を使用しました。npm5.2.0以上がインストールされたPCでターミナル(Winの場合はコマンドプロンプト)を開き、下記のコマンドを実行します。
1 2 3 | npx create-nuxt-app <project-name> |
あとは質問に回答して設定は完了です。
今回の記事は前提としてある程度Vueの知識が必要になりますが、見て楽しめることを目標にしていますので、知識のない方にも楽しんで頂けたら幸いです。
シンプルでシームレスなページ遷移
今回制作したのは記事一覧から記事詳細へのページ遷移を想定したものになります。
クリックした画像をそのまま遷移後のアイキャッチにすることで、視覚的に綺麗に感じられるように意識しました。
原理はとても簡単!
一見すると魔法のように見えますが、原理はとても簡単です。
クリックした画像の上にダミー用の画像を配置して、ページを透過します。
ダミー画像を遷移後の位置に動かしてページを切り替えた後に、ダミー画像を破棄するだけです。
実際にコードを見てみましょう。
遷移時のアニメーションはVue Routerのナビゲーションガードを使用しています。
ナビゲーションガードは、遷移前や遷移後に特定の処理を行う際に使用するもので、代表的な例だと会員制サイトのログイン状態やアクセスした動的なURLが存在するか調べる際に利用します。
まずはページを離れる際に処理を行いたいのでページ内で「beforeRouteLeave」を呼び出します。
こちらが呼び出されると、クリックした記事の情報(画像の位置やパス)をダミー画像に送ります。
続いてページの不透明度を0にして上までスクロールします。
(スクロールしないと記事詳細が変な位置に出ますので、SPAではページのスクロール位置を考えるのも非常に重要です)
今回アニメーションにはanime.jsを採用しています。
軽量で使いやすくそこそこ重宝するのでおすすめのライブラリです。
[pages/demo01/index.vue]
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | export default { beforeRouteLeave(to, from, next) { // クリックした記事の情報を取得 const component = this.$refs.item.find((x) => { return x.item.id === parseInt(to.params.id); }); const node = component.$refs.img; const listRect = this.$refs.list.getBoundingClientRect(); const itemRect = node.getBoundingClientRect(); // 遷移前の画像の位置を取得 const src = this.base + component.item.src; const styleObj = { top: `${itemRect.top - listRect.top}px`, left: `${itemRect.left - listRect.left}px`, width: `${node.clientWidth}px` } node.style.opacity = 0; // ダミー画像に位置と画像のURLを渡す this.$nuxt.$emit('layoutImage', { src: src, styleObj: styleObj }); // ページを上部に移動 anime({ targets: '#__nuxt', scrollTop: 0, easing: 'easeInOutQuart', duration: 800 }); // ページの不透明度を0にアニメーション anime({ targets: this.$refs.list, opacity: [1, 0], easing: 'easeInOutQuart', duration: 800, complete: () => next() }); } } |
ダミー画像を生成します。
ページから幅や位置、画像のパスなどが送られます。
[layouts/demo01.vue]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <template> ~~~ <img :src="src" :style="styleObj" v-if="styleObj.width !== ''" class="tmp" alt="" > ~~~ </template> export default { methods: { layoutImage({src, styleObj}) { this.src = src; this.styleObj = styleObj; } } } |
ページの不透明度が0になった時点でページの切り替えが行われます、続いて詳細ページで「beforeRouteEnter」を呼び出します。
ページが切り替わった時点ではまだページの不透明度を0にしておき、画像の位置を取得してダミー画像に渡します。
ここからページの不透明度を1にしますが、ポイントは画像自体はまだ透明にしておきます。
こうしないとダミー画像を移動している時に記事の画像も見えてしまいます。
[pages/demo01/_id/index.vue]
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | export default { beforeRouteEnter(to, from, next) { if(from.name === null) { next(vm => { vm.styleObj.opacity = 1; }); } else { // 遷移時にアニメーションを実行 next(vm => vm.setImageData()); } }, methods: { setImageData() { const node = this.$refs.img; const wrapRect = this.$refs.detail.getBoundingClientRect(); const itemRect = node.getBoundingClientRect(); // 遷移後の画像の位置を取得 const styleObj = { top: `${itemRect.top - wrapRect.top}px`, left: `${itemRect.left - wrapRect.left}px`, width: `${node.clientWidth}px` } node.style.opacity = 0; // ダミー画像に情報を渡す this.$nuxt.$emit('layoutImageMove', { styleObj: styleObj, node: node }); // ページの不透明度を1にする anime({ targets: this.styleObj, opacity: [0, 1], easing: 'easeInOutQuart', duration: 800 }); } } } |
そして最後に画像のダミー画像を移動して記事の画像と切り替えます。
「layoutImageMove」関数には記事の画像と位置が渡されていますので、
移動が完了した後に切り替えをして処理は終了です。
遷移時に記事や画像の不透明度の管理に気を付けている箇所もあるかと思いますが、
順番を追って見ると処理自体はシンプルな事がわかります。
[layouts/demo01.vue]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | export default { methods: { layoutImageMove({node, styleObj}) { anime({ targets: this.styleObj, top: styleObj.top, left: styleObj.left, width: styleObj.width, easing: 'easeInOutQuart', duration: 800, complete: () => { node.style.opacity = 1; this.styleObj = { top: '', left: '', width: '' } } }); } } } |
所感
アイデア次第ではもっと面白いサイトが作れますが、実装の難易度が高く費用対効果も考慮しなければいけないのが少し大変かと思いました。
SPAを使用するメリットとデメリット
「Web制作にSPAってどうなの?」と感じる人も多いかと思います。
そこで、SPAを使用するメリット・デメリットについて考えてみました。
メリット | ・初回の読み込み後は高速に動くのでユーザーを退屈させない ・ユーザーが体験したことのないUI/UXデザインを実現することができる |
---|---|
デメリット | ・初回の読み込みが長くなる傾向にある ・コストが増える ・SEOに不安が残る |
こうして書き出してみると、デメリットの方が目につきますね。
コストが増える
・動的なデザインにもこだわりが深く持てるので…
・全てプログラムによって開発する必要があるため…
といったさまざまな理由からコストが増えてしまいます。
SEOに不安が残る
SPAの場合、表示された時のHTMLがほぼ空になるため、「Googleのクローラーがjsの処理の終了まで待ってくれる保証がない」といった理由からデメリットとして広く知られています。(試しに上記のサンプルを開き、Chromeの場合は⌘+opt+u(winの場合はctrl+u)を押してソースを表示してみて下さい)
ですが、Web制作の発展に伴って、それらも少しずつ解消されていると感じます。
例えば、「SEOに不安が残る」に関して、近年ではSSR(サーバーサイドレンダリング)が解決策の一つとして挙げられています。SPAの場合はフロントサイドでレンダリングをおこなうため、HTMLは生成されない状態で表示されますが、SSRの場合はサーバーサイドでレンダリングをおこなうため、HTMLが生成された状態で表示されます。
Nuxt.jsは柔軟なフレームワークを提唱しており、SSRでの開発も可能となっています。ただし、実際にWebサイトとして公開するためには、サーバーでnode.jsを動かす必要がありますのでレンタールサーバーではなくVPN等を使用する必要があります。
コスト面に関しても、フレームワークを使用することによって、環境を一から構築するよりも大幅に削減できているかと思います。
そういった面を考えると、「ユーザーが体験したことのないUI/UXデザインを実現することができる」というメリットも光って見えてきて、これからのWeb制作の可能性も少しずつ広がってきているのはないでしょうか。
サイト紹介
SPAで作成された美麗なWebサイトをいくつか紹介させて頂きます。
B.Nature
ハワイでの結婚式を展開するB.Narureのブランドサイトです。下層ページへのリンクをクリックすると、ハワイの海を想像させるような青い波が画面を流れます。
Yuto Takahashi
フリーランスの写真家、デザイナー、エンジニアとして活動しているYuto Takahashi氏のポートフォリオサイトです。各作品をクリックすると、画面がスライドしながら詳細に飛びます。ページ遷移だけでなく、スクロールやホバーエフェクトもこだわりが強く、格好良くて唸ってしまう動きだと思いました。
Spatzel Studio
オーストラリアのウェブ制作会社です。下層ページに飛ぼうとすると、画面全体がブラインドの閉まるような動きをします。洗練されたアニメーションが、リッチなサイトだなと印象付ける一翼を担っています。
まとめ
SPAでのサイト制作は、実装するスキルはもちろんですがアイデアもとても重要で、多くのサイトを見ながらデザイン力や発想を磨くのが良いかと思います。
Nuxt.jsはVueファイルで書かれており、面倒なルーティングの設定もフォルダの階層から自動でおこなってくれるため、サイトの調整に時間を掛けることができました。
今回は時間の都合で1つのみのデモページですが、余力があれば複雑なものも作りたいですね。
コスト面を見てもまだまだ発展途上な技術かと思いますが、この記事を読んでWeb制作の可能性を一人でも感じて頂けたら幸いです。
以上です。