この投稿は、スムーズな移行の作成に関するSmashing Magazineの記事「ページ移行によるユーザーフローの改善」の翻訳です 。 この記事の著者であるLuigi De Rosaは、 EPICのフロントエンド開発者です。 さらに、物語は記事の著者に代わって行われます。 良い読書をしてください。
ユーザーがインタラクション(UX)のエクスペリエンスに問題があるたびに、ユーザーが離脱する機会が増えます。 あるページから別のページに変更すると、多くの場合、コンテンツのない白いフリッカーの形で中断が発生し、負荷が長くなるか、以前に開いたページのコンテキストからユーザーが引き離されます。
ページ間の遷移は、ユーザーコンテキストを保持(または改善さえ)し、ユーザーの注意を保持し、視覚的な継続性を提供することにより、このエクスペリエンスを強化できます。 同時に、ページ間の遷移は目を楽しませ、優れたパフォーマンスで興味深いものになります。
この記事では、ページ遷移を段階的に作成します。 また、この手法の長所と短所、および最大限に活用する方法についても説明します。
例
多くのモバイルアプリは、ビュー間の優れた移行を使用します。 Googleのマテリアルデザインガイドラインに従ったこの例では、アニメーションがページ間の階層的および空間的な関係をどのように伝えるかを確認します。
ウェブサイトに対して同様のアプローチをとらないのはなぜですか? ユーザーがページの変更ごとにテレポートされているように感じることに同意するのはなぜですか?
ページ遷移をリンクする方法
SPAフレームワーク
手を汚す前に、単一ページアプリケーション(SPA)フレームワークについて何か言わなければなりません。 SPAフレームワーク(AngularJS、Backbone.js、Emberなど)を使用する場合、すべてのパスがJavaScriptによって処理されるため、遷移の作成がはるかに簡単になります。 この場合、適切な例と指示があるため、関連するドキュメントを参照して、選択したフレームワークのページ間の遷移の実装を確認する必要があります。
悪い方法
ページ間の遷移を作成する最初の試みは次のようになりました。
document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });
概念は単純です。ユーザーがページを離れるときにアニメーションを使用し、新しいページが読み込まれるときに別のアニメーションを使用します。
しかし、このソリューションには多くの制限があることにすぐに気付きました。
- ページの読み込み時間はわかりませんので、アニメーションがスムーズに表示されない場合があります。
- 前のページと次のページのコンテンツを組み合わせたトランジションを作成することはできません。
実際、スムーズな移行を実現する唯一の方法は、ページを変更するプロセスを完全に制御することであり、したがって、ページ全体を変更することはできません。
したがって、問題へのアプローチを変更する必要があります。
正しい方法
正しい方法でページ間のシンプルでスムーズな移行を作成するための手順を見てみましょう。 pushState
AJAX(またはPJAX)ナビゲーションと呼ばれるものがあり、これは基本的に私たちのサイトを1ページのサイトのようなものに変えます。
これは、スムーズで快適な移行を実現する方法であるだけでなく、この記事の後半で詳しく説明する他の利点も活用します。
デフォルトのリンク動作を防ぐ
最初のステップは、すべてのリンクのクリックイベントハンドラーを作成し、リンクが標準の動作を行わないようにし、ページの変更の処理方法を変更することです。
// , // , , . document.addEventListener('click', function(e) { var el = e.target; // , .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });
特定のノードに追加するのではなく、親要素にハンドラーを追加するこの方法は、 イベントの委任と呼ばれます 。これは、HTML DOM API バブルイベントの性質により可能です。
取得ページ
ブラウザによるページの読み込みを中断したので、 Fetch APIを使用してページを手動で取得できます 。 URLを受け取ったときにHTMLページのコンテンツを受け取る次の関数を見てみましょう。
function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }
Fetch APIをサポートしていないブラウザーの場合、 polyfillを追加するか 、 XMLHttpRequestを使用する必要があります。
現在のURLを変更
HTML5にはpushState
と呼ばれる素晴らしいAPIがあり、Webサイトがページを読み込まずにブラウザーの履歴にアクセスして変更できるようにします。 以下では、これを使用して、現在のURLを次のページのURLに変更します。 これは、以前に発表されたクリックハンドラーの修正であることに注意してください。
if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }
お気づきかもしれませんが、 changePage
関数の呼び出しも追加しました。 changePage
関数の詳細を確認します。 同様の関数は、 popstate
イベントでも呼び出されます。これは、アクティブなブラウザーの履歴が変更されたとき(たとえば、ユーザーが[戻る]ボタンを押したとき)に発生します。
window.addEventListener('popstate', changePage);
したがって、アクティブモードとパッシブモードを持つ非常に原始的なルーティングシステムを構築しています。
ユーザーがリンクをクリックするとアクティブモードが発生し、 pushState
を使用してURLを変更しますが、URLが変更されるとパッシブモードが発生し、 popstate
イベントから通知を受け取ります。 いずれにしても、 changePage
を呼び出して、新しいURLの読み取りとページの読み込みを処理します。
新しいコンテンツの解析と追加
通常、ナビゲートしているページには、ヘッダーやフッターなどの基本的な要素があります。 すべてのページで次のDOM構造を使用します(それ自体はSmashing Magazineの構造です)。
<header> … </header> <main> <div class="cc"> … </div> </main> <footer> … </footer>
各ページで変更する必要があるのは、 cc
コンテナのコンテンツのみです。 したがって、 changePage
ようなchangePage
関数を作成できます。
var main = document.querySelector('main'); function changePage() { // , URL 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
コンテナーがあります。1つ目は前のページに属し、2つ目は次のページに属します。
次の関数であるanimate
は、古いコンテナを非表示にして、新しいコンテナを表示し、古いコンテナを削除する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で入手できます 。
これらはすべて、ページナビゲーションの基本です。
注意と制限
作成したばかりの小さな例は理想とはほど遠いものです。 実際、多くのことを考慮していません。
- 正しいリンクがあることを確認してください。
リンクの動作を変更する前に、変更が必要であることを確認するチェックを追加する必要があります。 たとえば、target="_blank"
(新しいタブでページを開く)を持つすべてのリンク、外部ドメインへのすべてのリンク、およびControl/Command + click
(新しいタブでページを開く)などの他の特殊なケースを無視する必要があります。 - メインコンテナ外のアイテムを更新します。
現時点では、ページが変更されている間、cc
コンテナーの外側のすべての要素は同じままです。 ただし、ドキュメントのtitle
、active
クラスのメニュー項目、および場合によってはサイト上の他の多くの依存関係など、これらの要素の一部を変更する必要があります(手動でのみ変更できるようになりました)。 - JavaScriptライフサイクル管理。
これで、ページはSPAとほぼ同じように動作します。SPAでは、ブラウザはそれ自体でページを変更しません。 そのため、特定のイベントのバインドと解放、ポリフィルやサードパーティコードなどのプラグインの実行など、JavaScriptのライフサイクルを手動で処理する必要があります。
ブラウザのサポート
このナビゲーションモードの唯一の要件はpushState
APIで、これはすべての最新のブラウザーで利用可能です 。 この方法は、進歩的な改善として完全に機能します。 ページは通常の方法で引き続きアクセスでき、JavaScriptが無効になっていてもWebサイトは正常に機能し続けます。
SPAフレームワークを使用している場合は、代わりにPJAXナビゲーションを使用して、ナビゲーションを高速化することを検討してください。 その見返りに、古いブラウザのサポートを取得し、よりSEOに優しいサイトを作成します。
進む
いくつかの側面を最適化することで、この方法を最大限に活用し続けることができます。 次のいくつかのトリックは 、ナビゲーションを高速化し 、ユーザーエクスペリエンスを大幅に改善します。
キャッシュの使用
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]; }); }
ご想像のとおり、 キャッシュAPIまたはユーザー側のその他の永続ストレージ(IndexedDBなど)で長期キャッシュを使用できます。
現在のページのアニメーション
フェードエフェクトでは、遷移が完了する前に次のページをロードして準備する必要があります。 リンクをクリックするとすぐに古いページでアニメーションを開始します。これにより、ユーザーは即応性とパフォーマンスの知覚を得ることができます。
promiseを使用すると、このような状況の処理は非常に簡単に見えるかもしれません。 .all
メソッドは、引数として渡されたすべてのプロミスが満たされた後に実行される新しいプロミスを作成します。
// animateOut() loadPage() Promise.all[animateOut(), loadPage(url)] .then(function(values) { …
次のページをプリロードする
PJAXナビゲーションを使用すると、ブラウザは新しいページのスクリプトやスタイルを解析および計算する必要がないため、ページはデフォルトのナビゲーションのほぼ2倍の速度で変更されます。
ただし、ユーザーがリンクにカーソルを合わせたときに次のページをプリロードすることにより、さらに先へ進むことができます。
ご覧のとおり、クリックとホバリングの間の遅延は通常200〜300ミリ秒です。 この時間は通常、次のページをロードするのに十分です。
しかし、これは私たちの側に簡単に来ることができます。 たとえば、リンクの長いリストがあり、ユーザーがリンクを介してページをスクロールする場合、リンクがカーソルの下にあるため、このメソッドはすべてのページをプリロードします。
気づいて考慮できるもう1つのポイントは、ユーザーの接続速度を予測することです。 (おそらくこれは、将来Network Information APIで可能になるでしょう。)
部分撤回
loadPage
関数loadPage
は、 cc
コンテナのみが必要ですが、HTMLドキュメント全体を取得します。 サーバー側で言語を使用した場合、リクエストが特定のAJAXユーザーコールからのものかどうかを確認し、そうであれば、目的のコンテナーのみを表示できます。 Headers 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'])) { // }
これにより、HTTPメッセージのサイズが削減され、サーバーの負荷が軽減されます。
まとめると
この手法を多くのプロジェクトに導入した後、再利用可能なライブラリが非常に役立つと思われました。 これにより、次回は時間を節約でき、遷移効果自体に集中できます。
このようにして生まれたBarba.js-小さなライブラリ(圧縮状態で4 KB)。この複雑さをすべて抽象化し、開発者にすてきできれいでシンプルなAPIを提供します。 また、さまざまな観点を考慮し、既成のトランジション、キャッシュ、プリロード、イベントが付属しています。 ライブラリはオープンソースであり、GitHubで入手できます 。
おわりに
スムーズなオーバーラップ効果を作成する方法、PJAXナビゲーションを使用して効果的にサイトをSPAに変えることの長所と短所について見てきました。 移行自体の利点に加えて、新しいページのロードを高速化するために、単純なキャッシングとプリロードのメカニズムを導入することも検討しました。
この記事全体は、私の個人的な経験と、取り組んだプロジェクトの移行の実装中に学んだことを基にしています。