ページ遷移によるユーザーフローの改善
公開: 2022-03-10ページ間の遷移は、ユーザーのコンテキストを保持(または改善)し、ユーザーの注意を維持し、視覚的な継続性と肯定的なフィードバックを提供することで、エクスペリエンスを向上させることができます。 同時に、ページのトランジションは見た目にも美しく楽しいものであり、うまくいくとブランディングを強化することができます。

この記事では、ページ間のトランジションを段階的に作成します。 また、この手法の長所と短所、およびそれを限界まで押し上げる方法についても説明します。
例
多くのモバイルアプリは、ビュー間の遷移をうまく利用しています。 以下の例では、Googleのマテリアルデザインガイドラインに従っており、アニメーションがページ間の階層的および空間的な関係をどのように伝達するかを示しています。
私たちのウェブサイトで同じアプローチを使用しないのはなぜですか? ページが変わるたびにテレポートされているようにユーザーが感じても大丈夫なのはなぜですか?
Webページ間を移行する方法
SPAフレームワーク
手を汚す前に、シングルページアプリケーション(SPA)フレームワークについて何か言う必要があります。 SPAフレームワーク(AngularJS、Backbone.js、Emberなど)を使用している場合は、すべてのルーティングが既にJavaScriptによって処理されているため、ページ間のトランジションの作成がはるかに簡単になります。 おそらくいくつかの良い例とチュートリアルがあるので、選択したフレームワークを使用してページを移行する方法については、関連するドキュメントを参照してください。
間違ったやり方
ページ間のトランジションを作成する最初の試みは、多かれ少なかれ次のようになりました。
document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });
概念は単純です。ユーザーがページを離れるときに1つのアニメーションを使用し、新しいページが読み込まれるときに別のアニメーションを使用します。
ただし、このソリューションにはいくつかの制限があることがすぐにわかりました。
- 次のページの読み込みにかかる時間がわからないため、アニメーションが滑らかに見えない可能性があります。
- 前のページと次のページのコンテンツを組み合わせたトランジションを作成することはできません。
実際、スムーズでスムーズな移行を実現する唯一の方法は、ページ変更プロセスを完全に制御することであり、したがって、ページをまったく変更しないことです。 したがって、問題へのアプローチを変更する必要があります。
正しい方法
ページ間の単純なクロスフェードトランジションを正しい方法で作成するための手順を見てみましょう。 これには、 pushState
AJAX(またはPJAX)ナビゲーションと呼ばれるものが含まれます。これにより、基本的に、当社のWebサイトが一種の単一ページのWebサイトに変わります。
この手法は、スムーズで快適な移行を実現するだけでなく、この記事の後半で詳しく説明する他の利点からも恩恵を受けます。
デフォルトのリンク動作を防ぐ
最初のステップは、使用するすべてのリンクのclick
イベントリスナーを作成し、ブラウザーがデフォルトの動作を実行しないようにし、ページの変更を処理する方法をカスタマイズすることです。
// Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });
イベントリスナーを特定の各ノードに追加するのではなく、親要素に追加するこの方法は、イベント委任と呼ばれ、HTML DOMAPIのイベントバブリングの性質により可能になります。
ページを取得する
ページを変更しようとしたときにブラウザに割り込んだので、FetchAPIを使用してそのページを手動でフェッチできます。 次の関数を見てみましょう。この関数は、URLが指定されたときにページのHTMLコンテンツをフェッチします。
function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }
Fetch APIをサポートしていないブラウザーの場合は、ポリフィルを追加するか、古き良きXMLHttpRequest
を使用することを検討してください。
現在のURLを変更する
HTML5にはpushState
と呼ばれる素晴らしいAPIがあり、ウェブサイトがページを読み込まずにブラウザの履歴にアクセスして変更できるようにします。 以下では、これを使用して、現在のURLを次のページのURLに変更しています。 これは、以前に宣言されたアンカークリックイベントハンドラーの変更であることに注意してください。
if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }
お気づきかもしれませんが、 changePage
という名前の関数への呼び出しも追加しました。これについては、後ほど詳しく説明します。 同じ関数は、ブラウザのアクティブな履歴エントリが変更されたときに発生するpopstate
イベントでも呼び出されます(ユーザーがブラウザの戻るボタンをクリックしたときなど)。
window.addEventListener('popstate', changePage);
これらすべてを使用して、基本的に、アクティブモードとパッシブモードを備えた非常に原始的なルーティングシステムを構築しています。
アクティブモードは、ユーザーがリンクをクリックしてpushState
を使用してURLを変更するときに使用されます。一方、パッシブモードは、URLが変更され、 popstate
イベントによって通知されるときに使用されます。 いずれの場合も、 changePage
を呼び出します。これにより、新しいURLの読み取りと、関連するページの読み込みが行われます。
新しいコンテンツを解析して追加する
通常、ナビゲートされるページには、 header
やfooter
などの共通の要素があります。 すべてのページで次のDOM構造を使用するとします(これは実際にはSmashing Magazine自体の構造です)。
var main = document.querySelector('main'); function changePage() { // Note, the URL has already been changed var url = window.location.href; loadPage(url).then(function(responseText) { var wrapper = document.createElement('div'); wrapper.innerHTML = responseText; var oldContent = document.querySelector('.cc'); var newContent = wrapper.querySelector('.cc'); main.appendChild(newContent); animate(oldContent, newContent); }); }
アニメート!
ユーザーがリンクをクリックすると、 changePage
関数はそのページのHTMLをフェッチし、 cc
コンテナーを抽出して、それをmain
要素に追加します。 この時点で、ページには2つのcc
コンテナがあります。最初のコンテナは前のページに属し、2番目のコンテナは次のページに属します。

次の関数animate
は、2つのコンテナを重ね合わせ、古いコンテナをフェードアウトし、新しいコンテナをフェードインし、古いコンテナを削除することで、2つのコンテナのクロスフェードを処理します。 この例では、Web Animations APIを使用してフェードアニメーションを作成していますが、もちろん、任意のテクニックまたはライブラリを使用できます。
function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }
最終的なコードはGitHubで入手できます。
そして、これらはWebページの移行の基本です。
警告と制限
作成したばかりの小さな例は、完璧にはほど遠いです。 実際、まだいくつかのことを考慮していません。
- 正しいリンクに影響を与えることを確認してください。
リンクの動作を変更する前に、変更する必要があることを確認するためのチェックを追加する必要があります。 たとえば、target="_blank"
(新しいタブでページを開く)のあるすべてのリンク、外部ドメインへのすべてのリンク、およびControl/Command + click
(でページを開く)などの他の特殊なケースを無視する必要があります。新しいタブ)。 - メインコンテンツコンテナの外部の要素を更新します。
現在、ページが変更されても、cc
コンテナの外側のすべての要素は同じままです。 ただし、ドキュメントのtitle
、active
クラスのメニュー要素、およびWebサイトによっては他の多くの要素を含め、これらの要素の一部を変更する必要があります(現在は手動でのみ実行できます)。 - JavaScriptのライフサイクルを管理します。
これで、ページはSPAのように動作し、ブラウザはページ自体を変更しません。 そのため、JavaScriptのライフサイクルを手動で処理する必要があります。たとえば、特定のイベントのバインドとバインド解除、プラグインの再評価、ポリフィルとサードパーティコードの組み込みなどです。
ブラウザのサポート
私たちが実装しているこのナビゲーションモードの唯一の要件は、 pushState
APIです。これは、最新のすべてのブラウザーで使用できます。 この手法は、プログレッシブエンハンスメントとして完全に機能します。 ページは引き続き提供され、通常の方法でアクセスできます。JavaScriptが無効になっている場合でも、Webサイトは引き続き正常に機能します。
SPAフレームワークを使用している場合は、ナビゲーションを高速に保つために、代わりにPJAXナビゲーションの使用を検討してください。 そうすることで、レガシーサポートを取得し、よりSEOに適したWebサイトを作成します。
さらに進んで
この手法の特定の側面を最適化することで、この手法の限界を押し上げることができます。 次のいくつかのトリックは、ナビゲーションを高速化し、ユーザーエクスペリエンスを大幅に向上させます。
キャッシュの使用
loadPage
関数を少し変更することで、単純なキャッシュを追加できます。これにより、既にアクセスされたページが再ロードされないようになります。
var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }
ご想像のとおり、Cache APIまたは別のクライアント側の永続ストレージキャッシュ(IndexedDBなど)でより永続的なキャッシュを使用できます。
現在のページをアニメーション化する
クロスフェード効果では、遷移が完了する前に次のページをロードして準備する必要があります。 別の効果として、ユーザーがリンクをクリックするとすぐに古いページのアニメーション化を開始したい場合があります。これにより、ユーザーはすぐにフィードバックを受け取り、パフォーマンスを向上させることができます。
promiseを使用することにより、このような状況の処理が非常に簡単になります。 .all
メソッドは、引数として含まれるすべてのPromiseが解決されるとすぐに解決される新しいPromiseを作成します。
// As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …
次のページのプリフェッチ
ブラウザが新しいページのスクリプトやスタイルを解析および評価する必要がないため、PJAXナビゲーションのみを使用すると、ページの変更は通常、デフォルトのナビゲーションのほぼ2倍の速度になります。
ただし、ユーザーがリンクにカーソルを合わせたり、リンクに触れ始めたりしたときに、次のページのプリロードを開始することで、さらに先に進むことができます。
ご覧のとおり、ユーザーのホバリングとクリックには通常200〜300ミリ秒の遅延があります。 これはデッドタイムであり、通常は次のページをロードするのに十分です。
そうは言っても、ボトルネックになりやすいため、賢明にプリフェッチします。 たとえば、リンクの長いリストがあり、ユーザーがそれをスクロールしている場合、リンクはマウスの下を通過するため、この手法ではすべてのページがプリフェッチされます。
プリフェッチするかどうかを決定する際に検出および考慮できるもう1つの要素は、ユーザーの接続速度です。 (多分これはネットワーク情報APIで将来可能になるでしょう。)
部分出力
loadPage
関数では、HTMLドキュメント全体をフェッチしていますが、実際にはcc
コンテナーのみが必要です。 サーバー側の言語を使用している場合は、リクエストが特定のカスタムAJAX呼び出しからのものであるかどうかを検出し、そうである場合は、必要なコンテナーのみを出力できます。 ヘッダーAPIを使用すると、フェッチリクエストでカスタムHTTPヘッダーを送信できます。
function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }
次に、サーバー側(この場合はPHPを使用)で、必要なコンテナーのみを出力する前に、カスタムヘッダーが存在するかどうかを検出できます。
if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }
これにより、HTTPメッセージのサイズが削減され、サーバー側の負荷も軽減されます。
まとめ
いくつかのプロジェクトでこの手法を実装した後、再利用可能なライブラリが非常に役立つことに気付きました。 毎回それを実装する時間を節約でき、トランジション効果自体に集中することができます。
このようにして、Barba.jsが誕生しました。これは、この複雑さをすべて抽象化し、開発者が使用できる優れたクリーンでシンプルなAPIを提供する小さなライブラリ(4 KBを縮小してgZip化)です。 また、ビューを考慮し、再利用可能な遷移、キャッシュ、プリフェッチ、およびイベントが付属しています。 オープンソースであり、GitHubで入手できます。
結論
これで、クロスフェード効果を作成する方法と、PJAXナビゲーションを使用してWebサイトをSPAに効果的に変換することの長所と短所を見てきました。 移行自体の利点とは別に、新しいページの読み込みを高速化するための単純なキャッシュとプリフェッチのメカニズムを実装する方法も見てきました。
この記事全体は、私の個人的な経験と、私が取り組んできたプロジェクトにページ遷移を実装することから学んだことに基づいています。 ご不明な点がございましたら、遠慮なくコメントを残すか、Twitterで私にご連絡ください。私の情報は以下のとおりです。
SmashingMagの詳細:
- ユーザーエクスペリエンスデザインのスマートトランジション
- マルチデバイスの世界への移行における設計
- Webテクノロジーでネイティブエクスペリエンスを提供する