通過頁面轉換改善用戶流程

已發表: 2022-03-10
快速總結↬任何時候用戶的體驗被打斷,他們離開的機會就會增加。 從一個頁面切換到另一個頁面通常會通過顯示無內容的白色閃爍、加載時間過長或以其他方式使用戶脫離新頁面打開之前所處的上下文而導致此中斷。

頁面之間的轉換可以通過保留(甚至改善)用戶的上下文、保持他們的注意力以及提供視覺連續性和積極反饋來增強體驗。 同時,頁面過渡也可以在美學上令人愉悅和有趣,並且如果做得好可以加強品牌。

Page Transitions

在本文中,我們將逐步創建頁面之間的過渡。 我們還將討論這種技術的優缺點以及如何將其推向極限。

例子

許多移動應用程序都很好地利用了視圖之間的轉換。 在下面的示例中,它遵循 Google 的材料設計指南,我們看到動畫如何傳達頁面之間的層次和空間關係。

為什麼我們不對我們的網站使用相同的方法? 為什麼我們可以讓用戶感覺每次頁面更改時他們都被傳送了?

跳躍後更多! 繼續往下看↓

如何在網頁之間轉換

SPA 框架

在動手之前,我應該先談談單頁應用程序 (SPA) 框架。 如果您使用的是 SPA 框架(例如 AngularJS、Backbone.js 或 Ember),那麼在頁面之間創建過渡會容易得多,因為所有路由都已由 JavaScript 處理。 請參閱相關文檔以了解如何使用您選擇的框架轉換頁面,因為可能有一些很好的示例和教程。

錯誤的方法

我第一次嘗試在頁面之間創建過渡看起來或多或少是這樣的:

 document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });

這個概念很簡單:當用戶離開頁面時使用一個動畫,當新頁面加載時使用另一個動畫。

然而,我很快發現這個解決方案有一些局限性:

  • 我們不知道加載下一頁需要多長時間,因此動畫可能看起來不流暢。
  • 我們無法創建結合上一頁和下一頁內容的過渡。

事實上,實現流暢和平滑過渡的唯一方法是完全控制頁面更改過程,因此根本不更改頁面。 因此,我們必須改變解決問題的方法。

正確的方式

讓我們看看以正確的方式在頁面之間創建簡單的淡入淡出過渡所涉及的步驟。 它涉及一種叫做pushState AJAX(或 PJAX)導航的東西,它本質上會將我們的網站變成一種單頁網站。

這種技術不僅實現了平滑和愉快的過渡,而且我們還將受益於其他優勢,我們將在本文後面詳細介紹。

防止默認鏈接行為

第一步是為所有要使用的鏈接創建一個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 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 有一個很棒的 AP​​I,叫做pushState ,它允許網站在不加載任何頁面的情況下訪問和修改瀏覽器的歷史記錄。 下面,我們用它來修改當前 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 並加載相關頁面。

解析並添加新內容

通常,被導航的頁面將具有共同的元素,例如headerfooter 。 假設我們在所有頁面上使用以下 DOM 結構(這實際上是 Smashing Magazine 本身的結構):

動畫!

當用戶單擊一個鏈接時, changePage函數獲取該頁面的 HTML,然後提取cc容器並將其添加main元素中。 此時,我們的頁面上有兩個cc容器,第一個屬於上一頁,第二個屬於下一頁。

下一個函數animate負責通過重疊兩個容器、淡出舊容器、淡入新容器和移除舊容器來交叉淡入淡出。 在本例中,我使用 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,其中瀏覽器本身不會更改頁面。 因此,我們需要手動處理 JavaScript 生命週期——例如,綁定和取消綁定某些事件、重新評估插件以及包括 polyfill 和第三方代碼。

瀏覽器支持

我們正在實現的這種導航模式的唯一要求是pushState API,它在所有現代瀏覽器中都可用。 這種技術完全可以作為一種漸進增強。 這些頁面仍以通常的方式提供和訪問,並且當 JavaScript 被禁用時,網站將繼續正常工作。

如果您使用的是 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]; }); }

您可能已經猜到了,我們可以通過 Cache API 或其他客戶端持久存儲緩存(如 IndexedDB)使用更永久的緩存。

動畫出當前頁面

我們的淡入淡出效果要求在過渡完成之前加載並準備好下一頁。 通過另一個效果,我們可能希望在用戶單擊鏈接後立即開始為舊頁面設置動畫,這將為用戶提供即時反饋,對感知性能有很大幫助。

通過使用 Promise,處理這種情況變得非常容易。 .all方法創建一個新的 Promise,一旦所有作為參數包含的 Promise 都被解析,該 Promise 就會被解析。

 // As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …

預取下一頁

僅使用 PJAX 導航,頁面更改通常幾乎是默認導航的兩倍,因為瀏覽器不必解析和評估新頁面上的任何腳本或樣式。

但是,當用戶將鼠標懸停在鏈接上或開始觸摸鏈接時,我們可以通過開始預加載下一頁來走得更遠。

可以看到,用戶的懸停和點擊通常會有 200 到 300 毫秒的延遲。 這是死時間,通常足以加載下一頁。

話雖如此,明智地預取,因為它很容易成為瓶頸。 例如,如果您有一個很長的鏈接列表並且用戶正在滾動瀏覽它,則此技術將預取所有頁面,因為鏈接在鼠標下方通過。

在決定是否預取時,我們可以檢測並考慮的另一個因素是用戶的連接速度。 (也許這將在未來通過網絡信息 API 實現。)

部分輸出

在我們的loadPage函數中,我們正在獲取整個 HTML 文檔,但實際上我們只需要cc容器。 如果我們使用服務器端語言,我們可以檢測請求是否來自特定的自定義 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'])) { // Output just the container }

這將減少 HTTP 消息的大小,同時也減少服務器端的負載。

包起來

在幾個項目中實現了這項技術之後,我意識到一個可重用的庫將非常有用。 這將節省我在每個場合實施它的時間,讓我可以專注於過渡效果本身。

因此誕生了 Barba.js,這是一個小型庫(4 KB 壓縮和 gZip'd),它抽像出所有這些複雜性,並為開發人員提供了一個漂亮、乾淨和簡單的 API 供開發人員使用。 它還考慮了視圖,並帶有可重用的轉換、緩存、預取和事件。 它是開源的,可在 GitHub 上找到。

結論

我們現在已經了解瞭如何創建淡入淡出效果以及使用 PJAX 導航將我們的網站有效地轉換為 SPA 的優缺點。 除了轉換本身的好處之外,我們還看到瞭如何實現簡單的緩存和預取機制來加速新頁面的加載。

整篇文章都是基於我的個人經驗以及我在我從事的項目中實現頁面轉換所學到的知識。 如果您有任何問題,請隨時發表評論或在 Twitter 上與我聯繫——我的信息如下!

關於 SmashingMag 的進一步閱讀

  • 用戶體驗設計中的智能轉換
  • 向多設備世界過渡的設計
  • 使用 Web 技術提供原生體驗