Thêm tính năng điều hướng tức thì và chuyển đổi trang liền mạch vào ứng dụng web

1. Trước khi bắt đầu

Lớp học lập trình này hướng dẫn bạn cách thêm tính năng điều hướng tức thì và chuyển đổi trang liền mạch vào ứng dụng web mẫu bằng các API mới nhất mà Google Chrome hỗ trợ sẵn.

Ứng dụng web mẫu kiểm tra giá trị dinh dưỡng của các loại trái cây và rau củ phổ biến. Các trang về danh sách trái cây và thông tin chi tiết về trái cây được xây dựng dưới dạng ứng dụng một trang (SPA), còn các trang về danh sách rau củ và thông tin chi tiết về rau củ được xây dựng dưới dạng một ứng dụng nhiều trang (MPA) truyền thống.

Ảnh chụp màn hình ứng dụng mẫu trên thiết bị di động Ảnh chụp màn hình ứng dụng mẫu trên thiết bị di động

Cụ thể, bạn sẽ triển khai tính năng kết xuất trước, bộ nhớ đệm cho thao tác tiến/lùi (bfcache) và Proxy tìm nạp trước riêng tư để điều hướng tức thì và chuyển đổi thành phần gốc/được chia sẻ để chuyển đổi liền mạch trang. Bạn triển khai quá trình kết xuất trước và bfcache cho các trang MPA cũng như chuyển đổi phần tử dùng chung cho các trang SPA.

Tốc độ trang web luôn là một khía cạnh quan trọng trong trải nghiệm người dùng. Đó là lý do Google ra mắt Chỉ số quan trọng chính của trang web. Đây là một bộ chỉ số đo lường hiệu suất tải, tính tương tác và độ ổn định về hình ảnh của trang web nhằm đánh giá trải nghiệm người dùng trong thực tế. Các API mới nhất giúp bạn cải thiện điểm số Core Web Vitals của trang web tại hiện trường, đặc biệt là đối với hiệu suất tải.

hình ảnh minh hoạ cách bfcache cải thiện thời gian tải

Bản minh hoạ trên Mindvalley

Người dùng cũng đã quen với việc sử dụng các hiệu ứng chuyển đổi để khiến các thao tác điều hướng và thay đổi trạng thái trở nên cực kỳ trực quan trong các ứng dụng gốc dành cho thiết bị di động. Đáng tiếc là những trải nghiệm người dùng như vậy không hề đơn giản trên web. Mặc dù bạn có thể đạt được hiệu ứng tương tự với API nền tảng web hiện tại, nhưng việc phát triển có thể quá khó khăn hoặc phức tạp, đặc biệt là khi so sánh với các tính năng tương tự trong ứng dụng Android hoặc iOS. API liền mạch được thiết kế để lấp đầy khoảng cách trong trải nghiệm của người dùng và nhà phát triển giữa ứng dụng và web.

Bản minh hoạ API chuyển đổi phần tử dùng chung của pixiv Bản minh hoạ API chuyển đổi phần tử dùng chung của Tokopedia

Bản minh hoạ của pixiv Tokopedia

Điều kiện tiên quyết

Kiến thức về:

Bạn sẽ tìm hiểu về:

Cách triển khai:

  • Kết xuất trước
  • bfcache
  • Proxy tìm nạp trước riêng tư
  • Hiệu ứng chuyển đổi thành phần gốc/phần tử dùng chung

Sản phẩm bạn sẽ tạo ra

Ứng dụng web mẫu được tạo bằng Next.js được bổ sung các tính năng trình duyệt tức thì và liền mạch mới nhất:

  • Điều hướng gần như ngay lập tức với kết xuất trước
  • bfcache để tải tức thì bằng nút tiến và lùi của trình duyệt
  • Ấn tượng đầu tiên tuyệt vời khi điều hướng nhiều nguồn gốc bằng Proxy tìm nạp trước riêng tư hoặc cơ chế trao đổi có chữ ký (SXG)
  • Chuyển đổi liền mạch giữa các trang có chuyển đổi phần tử gốc/phần tử dùng chung

Bạn cần có

  • Chrome phiên bản 101 trở lên

2. Bắt đầu

Bật cờ Chrome

  1. Chuyển đến phần about://flags, sau đó bật cờ thời gian chạy Prerender2documentTransition API.
  2. Khởi động lại trình duyệt.

Lấy mã

  1. Mở đoạn mã qua kho lưu trữ GitHub trong môi trường phát triển yêu thích của bạn:
git clone -b codelab git@github.com:googlechromelabs/instant-seamless-demo.git
  1. Cài đặt các phần phụ thuộc cần thiết để chạy máy chủ:
npm install
  1. Khởi động máy chủ trên cổng 3000:
npm run dev
  1. Chuyển đến http://localhost:3000 trong trình duyệt của bạn.

Giờ đây, bạn có thể chỉnh sửa và cải thiện ứng dụng của mình. Bất cứ khi nào bạn thay đổi, ứng dụng sẽ tải lại và các thay đổi sẽ xuất hiện trực tiếp.

3. Tích hợp tính năng kết xuất trước

Trong bản minh hoạ này, thời gian tải trang thông tin chi tiết về rau củ trong ứng dụng mẫu rất chậm do độ trễ tuỳ ý ở phía máy chủ. Bạn loại bỏ thời gian chờ này bằng cách kết xuất trước.

Cách thêm nút kết xuất trước vào trang danh sách rau củ và cho phép các nút đó kích hoạt kết xuất trước sau khi người dùng nhấp vào:

  1. Tạo một thành phần nút, thẻ này sẽ tự động chèn thẻ tập lệnh quy tắc suy đoán:

components/prerender-button.js

import { useContext } from 'react'
import ResourceContext from './resource-context'

// You use resource context to manage global states.
// In the PrerenderButton component, you update the prerenderURL parameter when the button is clicked.
export default function PrerenderButton() {
  const { dispatch } = useContext(ResourceContext)
  const handleClick = (e) => {
    e.preventDefault()
    e.stopPropagation()
    const parent = e.target.closest('a')
    if (!parent) {
      return
    }
    const href = parent.getAttribute('href')
    dispatch({ type: 'update', prerenderURL: href })
  }

  return (
    <button className='ml-auto bg-gray-200 hover:bg-gray-300 px-4 rounded' onClick={handleClick}>
      Prerender
    </button>
  )
}
  1. Nhập thành phần PrerenderButton trong tệp list-item.js.

components/list-item.js

// Codelab: Add a PrerenderButton component.
import PrerenderButton from './prerender-button'

...
function ListItemForMPA({ item, href }) {
  return (
    <a href={href} className='block flex items-center'>
      <Icon src={item.image} />
      <div className='text-xl'>{item.name}</div>
      {/* Codelab: Add PrerenderButton component. */}
      <PrerenderButton />
    </a>
  )
}
  1. Tạo một thành phần để thêm Speculation Rules API (API Quy tắc suy đoán).

Thành phần SpeculationRules sẽ tự động chèn một thẻ tập lệnh vào trang khi ứng dụng cập nhật trạng thái prerenderURL.

components/speculationrules.js

import Script from 'next/script'
import { useContext, useMemo } from 'react'
import ResourceContext from './resource-context'

export default function SpeculationRules() {
  const { state } = useContext(ResourceContext)
  const { prerenderURL } = state

  return useMemo(() => {
    return (
      <>
        {prerenderURL && (
          <Script id='speculationrules' type='speculationrules'>
            {`
            {
              "prerender":[
                {
                  "source": "list",
                  "urls": ["${prerenderURL}"]
                }
              ]
            }
          `}
          </Script>
        )}
      </>
    )
  }, [prerenderURL])
}
  1. Tích hợp các thành phần với ứng dụng.

trang/_app.js

// Codelab: Add the SpeculationRules component.
import SpeculationRules from '../components/speculationrules'

function MyApp({ Component, pageProps }) {
  useAnalyticsForSPA()

  return (
    <ResourceContextProvider>
      <Layout>
        <Component {...pageProps} />
      </Layout>
      {/* Codelab: Add SpeculationRules component */}
      <SpeculationRules />
      <Script id='analytics-for-mpa' strategy='beforeInteractive' src='/analytics.js' />
    </ResourceContextProvider>
  )
}

export default MyApp
  1. Nhấp vào Kết xuất trước.

Giờ đây, bạn có thể thấy sự cải thiện đáng kể về khả năng tải. Trong trường hợp sử dụng thực tế, hệ thống sẽ kích hoạt quá trình kết xuất trước cho trang mà người dùng có khả năng sẽ truy cập tiếp theo bằng một số phương pháp phỏng đoán.

Video minh hoạ ứng dụng mẫu để kết xuất trước

Số liệu phân tích

Theo mặc định, tệp analytics.js trong ứng dụng web mẫu sẽ gửi một sự kiện xem trang khi sự kiện DOMContentLoaded diễn ra. Rất tiếc, đây không phải là việc nên làm vì sự kiện này sẽ kích hoạt trong giai đoạn kết xuất trước.

Cách giới thiệu sự kiện document.prerenderingprerenderingchange nhằm khắc phục vấn đề này:

  • Viết lại tệp analytics.js:

public/analytics.js

  const sendEvent = (type = 'pageview') => {
    // Codelab: Make analytics prerendering compatible.
    // The pageshow event could happen in the prerendered page before activation.
    // The prerendered page should be handled by the prerenderingchange event.
    if (document.prerendering) {
      return
    }
    console.log(`Send ${type} event for MPA navigation.`)
    fetch(`/api/analytics?from=${encodeURIComponent(location.pathname)}&type=${type}`)
  }
  ...

  // Codelab: Make analytics prerendering compatible.
  // The prerenderingchange event is triggered when the page is activated.
  document.addEventListener('prerenderingchange', () => {
    console.log('The prerendered page was activated.')
    sendEvent()
  })
  ...

Tuyệt vời! Bạn đã sửa đổi thành công số liệu phân tích của mình để chúng tương thích với quá trình kết xuất trước. Giờ đây, bạn có thể xem nhật ký lượt xem trang vào đúng thời điểm trong bảng điều khiển của trình duyệt.

4. Xoá trình chặn bfcache

Xoá trình xử lý sự kiện unload

Việc tạo sự kiện unload không cần thiết là một lỗi rất phổ biến và không nên dùng nữa. Việc này không chỉ ngăn bfcache hoạt động mà còn không đáng tin cậy. Ví dụ: trình duyệt này không phải lúc nào cũng kích hoạt trên thiết bị di độngSafari.

Thay vì dùng sự kiện unload, bạn sẽ sử dụng sự kiện pagehide. Sự kiện này sẽ kích hoạt trong mọi trường hợp khi sự kiện unload kích hoạt và khi một trang được đưa vào bfcache.

Cách xoá trình xử lý sự kiện unload:

  • Trong tệp analytics.js, hãy thay thế mã cho trình xử lý sự kiện unload bằng mã cho trình xử lý sự kiện pagehide:

public/analytics.js

// Codelab: Remove the unload event handler for bfcache.
// The unload event handler prevents the content from being stored in bfcache. Use the pagehide event instead.
window.addEventListener('pagehide', () => {
  sendEvent('leave')
})

Cập nhật tiêu đề kiểm soát bộ nhớ đệm

Các trang được phân phát với tiêu đề HTTP Cache-control: no-store không được hưởng lợi từ tính năng bfcache của trình duyệt, vì vậy bạn nên tiết kiệm với tiêu đề này. Cụ thể là nếu trang không chứa thông tin cá nhân hoá hoặc thông tin quan trọng, chẳng hạn như trạng thái đã đăng nhập, thì có thể bạn không cần phân phát trang đó bằng tiêu đề HTTP Cache-control: no-store.

Cách cập nhật tiêu đề kiểm soát bộ nhớ đệm của ứng dụng mẫu:

  • Sửa đổi mã getServerSideProps:

trang/rau/index.js

export const getServerSideProps = middleware(async (ctx) => {
  const { req, res } = ctx
  // Codelab: Modify the cache-control header.
  res.setHeader('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59')
  ...

trang/rau/[tên].js

export const getServerSideProps = middleware(async (ctx) => {
  const { req, res, query } = ctx
  // Codelab: Modify the cache-control header.
  res.setHeader('Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59')
  ...

Xác định xem một trang có được khôi phục từ bfcache hay không

Sự kiện pageshow kích hoạt ngay sau sự kiện load khi trang tải lần đầu và bất cứ khi nào trang được khôi phục từ bfcache. Sự kiện pageshow có thuộc tính persisted, giá trị này đúng nếu trang được khôi phục từ bfcache và giá trị này là false nếu không phải. Bạn có thể sử dụng thuộc tính persisted để phân biệt các lượt tải trang thông thường với các lượt khôi phục trong bfcache. Các dịch vụ phân tích chính cần biết về bfcache, nhưng bạn có thể kiểm tra xem trang có được khôi phục từ bfcache hay không và gửi sự kiện theo cách thủ công.

Cách xác định xem một trang có được khôi phục từ bfcache hay không:

  • Thêm mã này vào tệp analytics.js.

public/analytics.js

  // Codelab: Use the pageshow event handler for bfcache.
  window.addEventListener('pageshow', (e) => {
    // If the persisted flag exists, the page was restored from bfcache.
    if (e.persisted) {
      console.log('The page was restored from bfcache.')
      sendEvent()
    }
  })

Gỡ lỗi trang web

Công cụ cho nhà phát triển Chrome có thể giúp bạn kiểm tra các trang của mình để đảm bảo rằng các trang đó được tối ưu hoá cho bfcache và xác định bất kỳ vấn đề nào có thể khiến các trang đó không đủ điều kiện.

Cách kiểm tra một trang cụ thể:

  1. Chuyển đến trang đó trong Chrome.
  2. Trong Công cụ dành cho nhà phát triển Chrome, nhấp vào Ứng dụng > Bộ nhớ đệm cho thao tác tiến/lùi > Chạy kiểm thử.

Công cụ cho nhà phát triển Chrome cố gắng điều hướng rồi quay lại để xác định xem có thể khôi phục trang từ bfcache hay không.

49bf965af35d5324.pngs

Nếu thành công, bảng điều khiển sẽ thông báo cho bạn rằng trang đã được khôi phục từ bộ nhớ đệm cho thao tác tiến/lùi:

47015a0de45f0b0f.pngS

Nếu không thành công, bảng điều khiển sẽ thông báo cho bạn rằng trang chưa được khôi phục và lý do. Nếu bạn có thể giải quyết một vấn đề nào đó với tư cách là nhà phát triển, thì bảng điều khiển cũng sẽ cho bạn biết điều đó.

dcf0312c3fc378ce.png

5. Bật tính năng tìm nạp trước trên nhiều trang web

Quá trình tìm nạp trước bắt đầu tìm nạp sớm để các byte đã có sẵn tại trình duyệt khi người dùng di chuyển, giúp tăng tốc độ điều hướng. Đây là một cách dễ dàng để cải thiện Chỉ số quan trọng chính của trang web và bù đắp một số hoạt động mạng trước khi điều hướng. Việc này trực tiếp tăng tốc Thời gian hiển thị nội dung lớn nhất (LCP) và cung cấp thêm không gian cho Độ trễ đầu vào đầu tiên (FID) và Điểm số tổng hợp về mức thay đổi bố cục (CLS) trong quá trình điều hướng.

Proxy tìm nạp trước riêng tư cho phép tìm nạp trước trên nhiều trang web nhưng không tiết lộ thông tin riêng tư về người dùng cho máy chủ đích.

Cách hoạt động của Proxy tìm nạp trước riêng tư

Bật tính năng tìm nạp trước trên nhiều trang web bằng Proxy tìm nạp trước riêng tư

Chủ sở hữu trang web nắm quyền kiểm soát việc tìm nạp trước thông qua một tài nguyên tư vấn lưu lượng truy cập nổi tiếng, tương tự như /robots.txt cho trình thu thập dữ liệu web, cho phép máy chủ HTTP khai báo rằng các tác nhân triển khai cần áp dụng lời khuyên tương ứng. Hiện tại, chủ sở hữu trang web có thể yêu cầu nhân viên hỗ trợ không cho phép hoặc điều tiết kết nối mạng. Trong tương lai, chúng tôi có thể bổ sung thêm lời khuyên khác.

Cách lưu trữ tài nguyên tư vấn lưu lượng truy cập:

  1. Thêm tệp giống JSON này:

Public/.well-known/traffic-adlator

[
  {
    "user_agent": "prefetch-proxy",
    "google_prefetch_proxy_eap": {
      "fraction": 1
    }
  }
]

Trường google_prefetch_proxy_eap là trường đặc biệt dành cho chương trình tiếp cận sớm và trường fraction là trường kiểm soát tỷ lệ số lượt tìm nạp trước đã yêu cầu mà Proxy tìm nạp trước riêng tư gửi đi.

Thông báo lưu lượng truy cập sẽ được trả về với loại MIME application/trafficadvice+json.

  1. Trong tệp next.config.js, hãy định cấu hình tiêu đề phản hồi:

next.config.js

const nextConfig = {
  // Codelab: Modify content-type for traffic advice file.
  async headers() {
    return [
      {
        source: '/.well-known/traffic-advice',
        headers: [
          {
            key: 'Content-Type',
            value: 'application/trafficadvice+json',
          },
        ],
      },
    ]
  },
}

module.exports = nextConfig

6. Tích hợp API chuyển đổi phần tử dùng chung

Khi người dùng điều hướng trên web từ trang này sang trang khác, nội dung mà họ thấy thay đổi đột ngột và bất ngờ khi trang đầu tiên biến mất và trang mới xuất hiện. Trải nghiệm người dùng theo trình tự và bị ngắt kết nối này đang làm họ mất phương hướng và dẫn đến tải trọng nhận thức cao hơn bởi vì người dùng buộc phải phân tích cách thức họ đến được vị trí của họ. Ngoài ra, trải nghiệm này sẽ làm tăng mức độ cảm nhận của người dùng về việc trang tải trong khi họ chờ trang đích mong muốn tải.

Hoạt ảnh tải mượt mà sẽ giúp giảm tải nhận thức vì người dùng vẫn ở trong ngữ cảnh khi họ di chuyển giữa các trang và giảm độ trễ tải có thể nhận thấy vì người dùng thấy nội dung nào đó hấp dẫn và thú vị trong thời gian chờ đợi. Vì những lý do này, hầu hết các nền tảng đều cung cấp các lớp gốc dễ sử dụng, cho phép nhà phát triển xây dựng quá trình chuyển đổi liền mạch, chẳng hạn như Android, iOS, MacOS và Windows.

API chuyển đổi phần tử dùng chung cung cấp cho nhà phát triển một chức năng tương tự trên web, bất kể chuyển đổi là trên nhiều tài liệu (MPA) hay trong tài liệu (SPA).

Bản minh hoạ API chuyển đổi phần tử dùng chung của pixiv Bản minh hoạ API chuyển đổi phần tử dùng chung của Tokopedia

Bản minh hoạ của pixiv Tokopedia

Cách tích hợp API chuyển đổi phần tử dùng chung cho phần SPA của ứng dụng mẫu:

  1. Tạo một hook tuỳ chỉnh để quản lý hiệu ứng chuyển đổi trong tệp use-page-transition.js:

utils/use-page-transition.js

import { useEffect, useContext, useRef, useCallback } from 'react'
import ResourceContext from '../components/resource-context'

// Call this hook on this first page before you start the page transition. For Shared Element Transitions, you need to call the transition.start() method before the next page begins to render, and you need to do the Document Object Model (DOM) modification or setting of new shared elements inside the callback so that this hook returns the promise and defers to the callback resolve.
export const usePageTransitionPrep = () => {
  const { dispatch } = useContext(ResourceContext)

  return (elm) => {
    const sharedElements = elm.querySelectorAll('.shared-element')
    // Feature detection
    if (!document.createDocumentTransition) {
      return null
    }

    return new Promise((resolve) => {
      const transition = document.createDocumentTransition()
      Array.from(sharedElements).forEach((elm, idx) => {
        transition.setElement(elm, `target-${idx}`)
      })
      transition.start(async () => {
        resolve()
        await new Promise((resolver) => {
          dispatch({ type: 'update', transition: { transition, resolver } })
        })
      })
    })
  }
}

// Call this hook on the second page. Inside the useEffect hook, you can refer to the actual DOM element and set them as shared elements with the transition.setElement() method. When the resolver function is called, the transition is initiated between the captured images and newly set shared elements.
export const usePageTransition = () => {
  const { state, dispatch } = useContext(ResourceContext)
  const ref = useRef(null)
  const setRef = useCallback((node) => {
    ref.current = node
  }, [])

  useEffect(() => {
    if (!state.transition || !ref.current) {
      return
    }
    const { transition, resolver } = state.transition
    const sharedElements = ref.current.querySelectorAll('.shared-element')
    Array.from(sharedElements).forEach((elm, idx) => {
      transition.setElement(elm, `target-${idx}`)
    })
    resolver()
    return () => {
      dispatch({ type: 'update', transition: null })
    }
  })

  return setRef
}
  1. Hãy gọi hook tuỳ chỉnh usePageTransitionPrep() trong trang danh sách, sau đó gọi hàm không đồng bộ để kích hoạt phương thức transition.start() bên trong sự kiện click.

Bên trong hàm này, các phần tử lớp shared-element được thu thập và đăng ký dưới dạng các phần tử dùng chung.

components/list-item.js

// Codelab: Add the Shared Element Transitions API.
import { usePageTransitionPrep } from '../utils/use-page-transition'
...

function ListItemForSPA({ item, href }) {
  // Codelab: Add Shared Element Transitions.
  const transitionNextState = usePageTransitionPrep()
  const handleClick = async (e) => {
    const elm = e.target.closest('a')
    await transitionNextState(elm)
  }
  return (
    <Link href={href}>
      <a className='block flex items-center' onClick={handleClick}>
        <Icon src={item.image} name={item.name} className='shared-element' />
        <div className='text-xl'>{item.name}</div>
      </a>
    </Link>
  )
}
  1. Trên trang chi tiết, hãy gọi hook usePageTransition() để hoàn tất hàm callback transition.start().

Trong lệnh gọi lại này, các phần tử dùng chung trên trang chi tiết cũng được đăng ký.

trang/trái cây/[tên].js

// Codelab: Add the Shared Element Transitions API.
import { usePageTransition } from '../../utils/use-page-transition'

const Item = ({ data }) => {
  const { name, image, amountPer, nutrition } = data
  // Codelab: Add the Shared Element Transitions API.
  const ref = usePageTransition()

  return (
    <div className={'flex flex-col items-center justify-center py-4 px-4 sm:flex-row'} ref={ref}>
      <div className='flex flex-col items-center sm:w-2/4'>
        <Image
          className='object-cover border-gray-100 border-2 rounded-full shared-element'
          src={image}
          width='240'
          height='240'
          alt={`picture of ${name}`}
        />
        <h1 className='text-4xl font-bold mt-4'>{name}</h1>
      </div>

      <div className='sm:w-2/4 w-full'>
        <Nutrition amountPer={amountPer} nutrition={nutrition} />
      </div>
    </div>
  )
...
}

Giờ đây, bạn có thể thấy rằng các thành phần hình ảnh được chia sẻ trên danh sách và trang chi tiết, đồng thời được kết nối liền mạch trong quá trình chuyển đổi trang. Bạn thậm chí có thể tuỳ chỉnh ảnh động để ảnh động trở nên đẹp mắt hơn bằng phần tử giả của CSS.

Video minh hoạ ứng dụng mẫu không có hiệu ứng chuyển đổi phần tử dùng chung Video minh hoạ ứng dụng mẫu có hiệu ứng chuyển đổi phần tử dùng chung

7. Xin chúc mừng

Xin chúc mừng! Bạn đã tạo ra một ứng dụng web tức thì và liền mạch với trải nghiệm người dùng đơn giản, hấp dẫn và trực quan.

Tìm hiểu thêm

Kết xuất trước

bfcache

Tìm nạp trước trên nhiều trang web

Trao đổi có chữ ký

Hiệu ứng chuyển đổi thành phần gốc/phần tử dùng chung

Các API này vẫn đang ở giai đoạn phát triển ban đầu. Vì vậy, vui lòng chia sẻ ý kiến phản hồi của bạn tại crbug.com hoặc nếu bạn gặp vấn đề trong kho lưu trữ GitHub của các API có liên quan.