前端效能和載入體驗優化實踐(附:PWA、離線包、記憶體優化、預渲染)

後除發表於2021-12-28

前端效能和載入體驗優化實踐

一、背景:頁面為何會卡?

1.1 等待時間長(效能)

  1. 專案本身包/第三方指令碼比較大。
  2. JavaScript 執行阻塞頁面載入。
  3. 圖片體積大且多。

特別是對於首屏資源載入中的白屏時間,使用者等待的時間就越長,使用者感知到頁面的速度就越慢。麻省理工學院的 Richard Larson 在講話中指出,“人類將被動等待高估了 36%”(https://mazey.cn/t/em)。這意味著使用者感覺到的等待時間比開發工具記錄的長得多。

1.2 看起來卡(體驗)

頁面結構不斷調整,不連貫。抖動的頁面往往讓使用者感覺很卡。

頁面載入過程

二、優化效能

2.1 構建縮包,按需載入

2.1.1 NPM

首先通過 Webpack 外掛 webpack-bundle-analyzer 分析出專案中用到的 NPM 包及大小。

webpack-bundle-analyzer

結合專案可以分析出哪些包可以去除,哪些包可以有更好的替代品。

名稱 體積大小(Parsed) 說明 優先順序 信心指數
mint-ui 96.05KB 目前引入全部元件,需要按需載入 ⭐️⭐️⭐️⭐️
moment 95.51KB 時間戳格式化的庫,因為無法按需載入,目標是替換為可按需載入的 date-fns ⭐️⭐️⭐️⭐️
quill 213.31KB 富文字編輯器 ⭐️⭐️⭐️
whatwg-fetch 9.25KB 原生 fetch 的墊片,已存在 axios,需要統一 ⭐️
ua-device 148.48KB 使用 Navigator 代替 ⭐️⭐️⭐️⭐️⭐️
assets 546.11KB 整個專案的小體積圖片,大部分需要替換成連結引入或者分模組按需載入 ⭐️⭐️⭐️⭐️⭐️

然後在專案中移除或替換無用包,以及部分包的按需載入。

mint-ui 按需載入示例:

import { Swipe, SwipeItem, Progress, Navbar, TabItem, TabContainer, TabContainerItem, Lazyload } from 'mint-ui';

Vue.use(Lazyload);
Vue.component(Swipe.name, Swipe);
Vue.component(SwipeItem.name, SwipeItem);
Vue.component(Progress.name, Progress);
Vue.component(Navbar.name, Navbar);
Vue.component(TabItem.name, TabItem);
Vue.component(TabContainer.name, TabContainer);
Vue.component(TabContainerItem.name, TabContainerItem);

2.1.2 外鏈

不影響頁面主邏輯的外鏈往往不是很穩定,一定要等首屏載入完成以後按需載入。

示例:

// 載入其它資源
if (canLoad()) {
    let s = document.createElement("script");
    s.onload = () => {
        // ...
    };
    s.setAttribute(
        "src",
        "https://example.mazey.net/sdk.js"
    );
    document.body.appendChild(s);
}

2.2 減少圖片體積

2.2.1 調整尺寸

一般來說尺寸越大,圖片質量越高,則體積越大;相應的減少圖片的尺寸體積會變小,但質量也會變差一些,這裡就需要按照產品需求在效能和體驗上尋求一個平衡。

以一個尺寸 400x400 的 GIF 圖為例,尺寸轉為 200x200 之後,體積由 700k 減少到 238k(-66%)。

調整尺寸

2.2.2 GIF 轉 WebM

GIF 作為一個存在了長達 20 年的格式,相容性當然是最好的,但是其體積和質量對比現在流行的其他格式已經沒啥優勢了。目前動圖常見的表現格式是 APNG、WebP。

  • APNG(Animated Portable Network Graphics)
    基於 PNG(Portable Network Graphics)格式擴充套件的一種動畫格式,增加了對動畫影像的支援,同時加入了 24 點陣圖像和 8 位 Alpha 透明度的支援,這意味著動畫將擁有更好的質量,其誕生的目的是為了替代老舊的 GIF 格式,但它目前並沒有獲得 PNG 組織官方的認可。APNG 被 Mozilla 社群所推崇,2008 年首次在 Mozilla Firefox 中獲得支援,2017 年 Google Chrome 開始支援 APNG,截止到現在主流瀏覽器中只有微軟家的 IE 和 Edge 不支援 APMG。
  • WebP
    最初在2010年由 Google 釋出,目標是減少檔案大小,但達到和JPEG格式相同的圖片質量,希望能夠減少圖片檔在網路上的傳送時間。WebP 有靜態與動態兩種模式。動態WebP(Animated WebP)支援有損與無失真壓縮、ICC 色彩配置、XMP 詮釋資料、Alpha 透明通道。現在主流瀏覽器中只有 Google Chrome 和 Opera 支援 WebP。

以一個 GIF圖 為例,格式轉為 WebP 之後,體積由 238k 減少到 133k(-44%)。

轉圖片格式

但是 133k 的體積依舊很大,讓人難以接受。作為動畫效果,只要讓視訊迴圈播放,就能達到和 GIF 一樣的效果,然後我又試了主流的 MP4、WebM。

轉視訊

在轉成 WebM(同樣是 Google 家的視訊格式)之後,體積由 238k 減少到 40k(-83%)。在使用過程中加上迴圈播放,去除控制元件和載入完成後再渲染就達到了和 GIF 一樣的視覺效果。

示例:

<video autoplay muted name="media" loop poster="https://test.mazey.net/poster.jpg"
>
    <source src="https://test.mazey.net/source.webm" type="video/webm"
    >
</video>

2.2.3 PNG/JPG 壓縮

圖片上傳前先通過工具壓縮下(例如:https://tinypng.com/),正常都會有 50~80% 的減少。

tinypng

2.2.4 PNG/JPG 轉 WebP

PNG/JPG 轉 WebP 後圖片體積減少了 4-7 倍。

轉 WebP

2.2.5 SVG 壓縮

很多向量編輯器在匯出 SVG 檔案的時候,會附帶很多冗餘資訊。

附帶很多冗餘資訊的 SVG

經過 SVGO 類工具壓縮之後,體積往往會縮減約 30%。

經過 SVGOMG 壓縮過的 SVG

在專案中可以使用 Webpack svgo-loader 自動壓縮。

module.exports = {
  ...,
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [
          {
            loader: 'file-loader'
          },
          {
            loader: 'svgo-loader',
          }
        ]
      }
    ]
  }}

2.3 延遲埋點上報

大量業務上的埋點上報會阻塞圖片載入,保證首屏渲染完成後再執行上報。

2.4 preconnect 預連線域名

頁面中使用到的各種資源的域名較多,使用 preconnect 可以提前解析 DNS、TLS 協議、TCP 握手,節約後面載入資源時的網路請求時間。

<link href="https://cdn.domain.com" rel="preconnect">

2.5 禁掉 favicon.ico(Webview 場景)

瀏覽器載入頁面時,若沒有指定 icon,會預設請求一個根目錄下的 favicon.ico 檔案,作為手機內嵌的 H5 頁面,往往不需要展示圖示,為了節約這個請求可以通過在 <head> 裡面加上 <link rel="icon" href="data:;base64,="> 禁掉 favicon.ico 網路請求,畢竟弱網條件下,一個網路請求相當於 500ms。

2.6 啟動 Gzip/Brotli 壓縮

2.6.1 Gzip

Gzip 是一種用於檔案壓縮與解壓縮的檔案格式。原本是 UNIX 系統的檔案壓縮,後來逐漸成為 Web 最流行的資料壓縮格式。它基於 Deflate 演算法,可將檔案無失真壓縮地更小,對於純文字檔案,大概可以縮減 60% 的體積,從而實現更快的網路傳輸,特別是對移動端非常重要。當前主流瀏覽器普遍地支援 Gzip,這意味著伺服器可以在傳送檔案之前自動使用 Gzip 壓縮檔案,而瀏覽器可以在接收檔案時自行解壓縮檔案。

圖為縮減了 64.9% 的 JavaScript 檔案

2.6.2 Brotli

Google 認為網際網路使用者的時間是寶貴的,他們的時間不應該消耗在漫長的網頁載入中,因此在 2015 年 9 月 Google 推出了無失真壓縮演算法 Brotli,特別側重於 HTTP 壓縮。Brotli 通過變種的 LZ77 演算法、Huffman 編碼以及二階文字建模等方式進行資料壓縮,與其他壓縮演算法相比,它有著更高的壓縮效率。針對常見的 Web 資源內容,Brotli 的效能相比 Gzip 提高了 17-25%。

除了 IE、Opera Mini 和百度瀏覽器,所有的主流瀏覽器都已經支援 Brotli。

Brotli 相容性

三、優化體驗

3.1 骨架圖

頁面載入中新增骨架圖,骨架圖根據頁面基本架構生成,相對於純白屏,體驗更好。

骨架圖

示例:

<body>
    <!--骨架圖-->
    <svg></svg>
    <!--內容-->
    <div id="container"></div>
</body>

3.2 圖片佔點陣圖/懶載入

圖片載入的時候設定佔點陣圖,提醒使用者這邊會載入圖片,不至於很突兀。

佔點陣圖

配合 v-lazy 實現示例:

img[lazy=loading] {
    background-size: contain;
    background-image: url(...) ;
}

懶載入示例:

const imageSrc = '...';
const imgLoad = new Image();
imgLoad.onload = () => {
    // 模擬設定圖片 src
    setImageSrc(imageSrc);
};
imgLoad.src = imageSrc;

3.3 頁面防抖

首屏佔位小圖示直接轉 Base64,必要模組設定高度,規避整個頁面的抖動。

預設一個高度,防止抖動

附錄 A PWA

桌面端 PWA 應用:

桌面端 PWA 應用

移動端新增到桌面:

移動端新增到桌面

A.1 什麼是 PWA

PWA(Progressive Web App - 漸進式網頁應用)是一種理念,由 Google Chrome 在 2015 年提出。PWA 它不是特指某一項技術,而是應用多項技術來改善使用者體驗的 Web App,其核心技術包括 Web App ManifestService WorkerWeb Push 等,使用者體驗才是 PWA 的核心。

PWA 主要特點如下:

  • 可靠 - 即使在網路不穩定甚至斷網的環境下,也能瞬間載入並展現。
  • 使用者體驗 - 快速響應,具有平滑的過渡動畫及使用者操作的反饋。
  • 使用者黏性 - 和 Native App 一樣,可以被新增到桌面,能接受離線通知,具有沉浸式的使用者體驗。

PWA 本身強調漸進式(Progressive),可以從兩個角度來理解漸進式,首先,PWA 還在不斷進化,Service Worker、Web App Manifest、Device API 等標準每年都會有不小的進步;其次,標準的設計向下相容,並且侵入性小,開發者使用新特性代價很小,只需要在原有站點上新增,讓站點的使用者體驗漸進式的增強。相關技術基準線:What makes a good Progressive Web App?

  • 站點需要使用 HTTPS。
  • 頁面需要響應式,能夠在平板和移動裝置上都具有良好的瀏覽體驗。
  • 所有的 URL 在斷網的情況下有內容展現,不會展現瀏覽器預設頁面。
  • 需要支援 Wep App Manifest,能被新增到桌面
  • 即使在 3G 網路下,頁面載入要快,可互動時間要短。
  • 在主流瀏覽器下都能正常展現。
  • 動畫要流暢,有使用者操作反饋。
  • 每個頁面都有獨立的 URL。

A.2 案例調研

A.2.1 米哈遊 - 崩壞3

訪問地址:https://bbs.mihoyo.com/bh3/

PWA:僅支援在 IOS 端新增到桌面。

A.2.2 阿里速賣通(AliExpress)

訪問地址:https://m.aliexpress.com/

PWA:使用 Google Workbox(CDN)

  1. 支援新增到桌面,manifest
  2. 支援快取,Service Worker

A.2.3 餓了麼

訪問地址:https://h5.ele.me/msite/#pwa=true

PWA:自研 - PWA 在餓了麼的實踐經驗

  1. 支援新增到桌面,manifest
  2. 支援快取和離線訪問,Service Worker

A.2.4 Instagram

左邊原生應用,右邊 PWA

訪問地址:https://www.instagram.com/

PWA:使用 Google Workbox

  1. 支援新增到桌面,manifest
  2. 支援快取,Service Worker

A.2.5 Twitter

訪問地址:https://mobile.twitter.com/home

PWA:Twitter 自研 - How we built Twitter Lite

  1. 支援新增到桌面,manifest
  2. 支援快取和離線訪問,Service Worker

除了正常的靜態資源以外,Twitter 把首頁也快取了下來。

離線狀態下有很好的使用者體驗,而不是顯示預設的瀏覽器頁面。

A.3 技術選型(Service Worker)

A.3.1 使用 Google Workbox 構建 Service Worker

A.3.1.1 什麼是 Workbox

Workbox 是一組庫,可以幫助開發者編寫 Service Worker,通過 CacheStorage API 快取資源。當一起使用 Service Worker 和 CacheStorage API 時,可以控制網站上使用的資源(HTML、CSS、JS、影像等)如何從網路或快取中請求,甚至允許在離線時返回快取的內容。

A.3.1.2 如何使用 Workbox

Workbox 是由許多 NPM 模組組成的。首先要從 NPM 中安裝它,然後匯入專案 Service Worker 所需的模組。Workbox 的主要特性之一是它的路由和快取策略模組。

路由和快取策略

Workbox 允許使用不同的快取策略來管理 HTTP 請求的快取。首先確定正在處理的請求是否符合條件,如果符合,則對其應用快取策略。匹配是通過返回真值的回撥函式進行的。快取策略可以是 Workbox 的一種預定義策略,也可以建立自己的策略。如下是一個使用路由和快取的基本 Service Worker。

import { registerRoute } from 'workbox-routing';
import {
  NetworkFirst,
  StaleWhileRevalidate,
  CacheFirst,
} from 'workbox-strategies';

// Used for filtering matches based on status code, header, or both
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Used to limit entries in cache, remove entries after a certain period of time
import { ExpirationPlugin } from 'workbox-expiration';

// Cache page navigations (html) with a Network First strategy
registerRoute(
  // Check to see if the request is a navigation to a new page
  ({ request }) => request.mode === 'navigate',
  // Use a Network First caching strategy
  new NetworkFirst({
    // Put all cached files in a cache named 'pages'
    cacheName: 'pages',
    plugins: [
      // Ensure that only requests that result in a 200 status are cached
      new CacheableResponsePlugin({
        statuses: [200],
      }),
    ],
  }),
);

// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
registerRoute(
  // Check to see if the request's destination is style for stylesheets, script for JavaScript, or worker for web worker
  ({ request }) =>
    request.destination === 'style' ||
    request.destination === 'script' ||
    request.destination === 'worker',
  // Use a Stale While Revalidate caching strategy
  new StaleWhileRevalidate({
    // Put all cached files in a cache named 'assets'
    cacheName: 'assets',
    plugins: [
      // Ensure that only requests that result in a 200 status are cached
      new CacheableResponsePlugin({
        statuses: [200],
      }),
    ],
  }),
);

// Cache images with a Cache First strategy
registerRoute(
  // Check to see if the request's destination is style for an image
  ({ request }) => request.destination === 'image',
  // Use a Cache First caching strategy
  new CacheFirst({
    // Put all cached files in a cache named 'images'
    cacheName: 'images',
    plugins: [
      // Ensure that only requests that result in a 200 status are cached
      new CacheableResponsePlugin({
        statuses: [200],
      }),
      // Don't cache more than 50 items, and expire them after 30 days
      new ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 60 * 60 * 24 * 30, // 30 Days
      }),
    ],
  }),
);

這個 Service Worker 使用一個網路優先的策略來快取導航請求(用於新的 HTML 頁面),當它狀態碼為 200 時,該策略將快取的頁面儲存在一個名為 pages 的快取中。使用 Stale While Revalidate strategy 快取 CSS、JavaScript 和 Web Worker,將快取的資源儲存在一個名為 assets 的快取中。採用快取優先的策略來快取影像,將快取的影像儲存在名為 images 的快取中,30 天過期,並且一次只允許 50 個。

預快取

除了在發出請求時進行快取(執行時快取)之外,Workbox 還支援預快取,即在安裝 Service Worker 時快取資源。有許多資源是非常適合預快取的:Web 應用程式的起始 URL、離線回退頁面以及關鍵的 JavaScript 和 CSS 檔案。

使用一個支援預快取清單注入的外掛(webpack 或 rollup)來在新的 Service Worker 中使用預快取。

import { precacheAndRoute } from 'workbox-precaching';

// Use with precache injection
precacheAndRoute(self.__WB_MANIFEST);

這個 Service Worker 將在安裝時預快取檔案,替換 self.__WB_MANIFEST,其中包含在構建時注入到 Service Worker 中的資源。

離線回退

讓 Web 應用在離線工作時感覺更健壯的常見模式是提供一個後退頁面,而不是顯示瀏覽器的預設錯誤頁面。通過 Workbox 路由和預快取,你可以在幾行程式碼中設定這個模式。

import { precacheAndRoute, matchPrecache } from 'workbox-precaching';
import { setCatchHandler } from 'workbox-routing';

// Ensure your build step is configured to include /offline.html as part of your precache manifest.
precacheAndRoute(self.__WB_MANIFEST);

// Catch routing errors, like if the user is offline
setCatchHandler(async ({ event }) => {
  // Return the precached offline page if a document is being requested
  if (event.request.destination === 'document') {
    return matchPrecache('/offline.html');
  }

  return Response.error();
});

如果使用者處於離線狀態,則返回快取的離線頁面的內容,而不是生成一個瀏覽器錯誤。

有了 Workbox,可以利用 Service Worker 的力量來提高效能,並給您的站點提供獨立於網路的優秀的使用者體驗。

A.3.2 自研 Service Worker

自研 Service Worker 更加靈活、可控,但是因為需要考慮到各種相容,研發成本較高。

A.4 技術實踐(Service Worker)

A.4.1 使用 CLI

安裝 Workbox:

npm install workbox-cli -D

npx workbox --help

按照引導配置 workbox-config.js

npx workbox wizard

根據配置生成 Service Worker 程式:

npx workbox generateSW workbox-config.js

由於實際靜態資源是掛載在 CDN 上面,需要修改預渲染資源的字首

Workbox CLI - generateSW - Configuration

// A transformation that prepended the origin of a CDN for any URL starting with '/assets/' could be implemented as:

const cdnTransform = async (manifestEntries) => {
  const manifest = manifestEntries.map(entry => {
    const cdnOrigin = 'https://example.com';
    if (entry.url.startsWith('/assets/')) {
      entry.url = cdnOrigin + entry.url;
    }
    return entry;
  });
  return {manifest, warnings: []};
};

更多快取配置可查閱官方文件

A.4.2 使用 Webpack

安裝:

npm install workbox-webpack-plugin --save-dev

Webpack 配置:

// Inside of webpack.config.js:
const WorkboxPlugin = require('workbox-webpack-plugin');
// Version info...
const id = `${page}-v${version}`;

module.exports = {
  // Other webpack config...

  plugins: [
    // Other plugins...

    // WIKI https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW#GenerateSW
    new WorkboxPlugin.GenerateSW({
        cacheId: `${id}-gsw`,
        // Do not precache images
        exclude: [/\.(?:png|jpg|jpeg|svg)$/, 'service-wroker.js'], // Page need refresh twice.
        // target dir
        swDest: `../dist/${page}/service-worker.js`,
        skipWaiting: true,
        clientsClaim: true,
        // Define runtime caching rules.
        // WIKI https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.RuntimeCachingEntry
        // Example https://gist.github.com/jeffposnick/fc761c06856fa10dbf93e62ce7c4bd57
        runtimeCaching: [
          // icon images
          {
            // Match any request that ends with .png, .jpg, .jpeg or .svg.
            urlPattern: /^https:\/\/cdn.example.com\/platform/, // /\.(?:png|jpg|jpeg|svg)$/,
            // Apply a cache-first strategy.
            handler: 'CacheFirst',
            options: {
              // Use a custom cache name.
              cacheName: `${id}-icon-images`,
              // Only cache 50 images, and expire them after 30 days
              expiration: {
                maxEntries: 50
              },
              // Ensure that only requests that result in a 200 status are cached
              cacheableResponse: {
                statuses: [0, 200]
              }
            }
          },
          // note images & others
          {
            // Match any request that ends with .png, .jpg, .jpeg or .svg.
            urlPattern: /^https:\/\/image.example.com/, // /\.(?:png|jpg|jpeg|svg)$/,
            // Apply a cache-first strategy.
            handler: 'CacheFirst',
            options: {
              // Use a custom cache name.
              cacheName: `${id}-note-images`,
              // Only cache 50 images, and expire them after 30 days
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24 * 30 // 30 Days
              },
              // Ensure that only requests that result in a 200 status are cached
              cacheableResponse: {
                statuses: [0, 200]
              }
            }
          }
        ]
      });
  ]
};

頁面中觸發 Service Work:

<script>
// Check that service workers are supported
if ('serviceWorker' in navigator) {
  // Use the window load event to keep the page load performant
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js');
  });
}
</script>

A.5 新增到桌面方案

A.5.1 manifest.json 配置

{
  "name": "不知不問",
  "short_name": "不知不問",
  "description": "yyds",
  "start_url": "/?entry_mode=standalone",
  "display": "standalone",
  "orientation": "portrait",
  "background_color": "#F3F3F3",
  "theme_color": "#F3F3F3",
  "icons": [
    {
      "src": "https://mazey.cn/fav/logo-dark-circle-32x32.png",
      "sizes": "32x32",
      "type": "image/png"
    },
    {
      "src": "https://mazey.cn/fav/logo-dark-circle-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "https://mazey.cn/fav/logo-dark-circle-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "https://mazey.cn/fav/logo-dark-circle-180x180.png",
      "sizes": "180x180",
      "type": "image/png"
    },
    {
      "src": "https://mazey.cn/fav/logo-dark-circle-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "https://mazey.cn/fav/logo-dark-circle-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "scope": "/"
}

A.5.2 <head> 配置

為網站配置開屏圖片、狀態列等。

<!--Mazey's favicon begin-->
<link rel="shortcut icon" type="image/png" href="https://mazey.cn/fav/logo-dark-circle-transparent-144x144.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://mazey.cn/fav/logo-dark-circle-transparent-32x32.png">
<link rel="apple-touch-icon" sizes="144x144" href="https://mazey.cn/fav/logo-dark-circle-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="https://mazey.cn/fav/logo-dark-circle-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://mazey.cn/fav/logo-dark-circle-180x180.png">
<link rel="apple-touch-icon" sizes="192x192" href="https://mazey.cn/fav/logo-dark-circle-192x192.png">
<link rel="apple-touch-icon" sizes="512x512" href="https://mazey.cn/fav/logo-dark-circle-512x512.png">
<!--Mazey's favicon end-->
<!--Mazey's pwa manifest.json-->
<link rel="manifest" href="/wp-content/themes/polestar/manifest.json">
<!-- 開機圖片 - begin -->
<!-- iPhone Xs Max (1242px × 2688px) -->
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)" href="https://i.mazey.net/asset/read/cat-lovers-1242x2688.jpg" sizes="1242x2688">
<!-- iPhone Xr (828px x 1792px) -->
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)" href="https://i.mazey.net/asset/read/cat-lovers-828x1792.jpg" sizes="828x1792">
<!-- iPhone X, Xs (1125px x 2436px) -->
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" href="https://i.mazey.net/asset/read/cat-lovers-1125x2436.jpg" sizes="1125x2436">
<!-- iPhone 8, 7, 6s, 6 (750px x 1334px) -->
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="https://i.mazey.net/asset/read/cat-lovers-750x1334.jpg" sizes="750x1334">
<!-- iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus (1242px x 2208px) -->
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3)" href="https://i.mazey.net/asset/read/cat-lovers-1242x2208.jpg" sizes="1242x2208">
<!-- iPhone 5 (640px x 1136px) -->
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="https://i.mazey.net/asset/read/cat-lovers-640x1136.jpg" sizes="640x1136">
<!-- 開機圖片 - end -->
<!-- Touch Bar區域顯示的網站圖示 -->
<link rel="mask-icon" href="https://mazey.cn/fav/logo-dark-circle.svg" color="#F3F3F3">
<!-- 主題色 = manifest.json theme_color -->
<meta name="theme-color" content="#F3F3F3">
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- 狀態列顏色 default/black/black-translucent -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- 應用名 -->
<meta name="apple-mobile-web-app-title" content="不知不問">
<!-- 在Windows 8上,我們可以將網站固定在開始螢幕上,而且支援個性化自定義色塊icon和背景圖片。這個標籤是用來定義色塊的背景圖的。色塊圖應該為144*144畫素的png格式圖片,背景透明。 -->
<meta name="msapplication-TileImage" content="https://mazey.cn/fav/logo-dark-circle-transparent-144x144.png">
<!-- 同前一個後設資料msapplication-TileImage類似,這個功能是用來設定顏色值,個性化自定義色塊(磁貼)icon -->
<meta name="msapplication-TileColor" content="#F3F3F3">

開屏圖片尺寸總結:

螢幕尺寸 倍數 圖片尺寸
1024x1366(512x683) x2 2048x2732
834x1194(417x597) x2 1668x2388
768x1024(384x512) x2 1536x2048
834x1112(417x556) x2 1668x2224
810x1080 x2 1620x2160
428x926(214x463) x3 1284x2778
390x844 x3 1170x2532
375x812 x3 1125x2436
414x896 x3 1242x2688
414x896 x2 828x1792
414x736 x3 1242x2208
375x667 x2 750x1334
320x568 x2 640x1136

附錄 B 客戶端快取支援

客戶端在頁面首次載入後把資源快取下來,之後每次載入不進行網路請求直接讀取快取,然後再對比本次請求的版本和線上的版本,若有更新再次快取以供下次訪問,極大的縮短白屏時間。缺點是有滯後性,永遠落後於線上一個版本。

附錄 C 客戶端離線包支援

為了解決客戶端快取的滯後問題,離線包方式是一種提前下載頁面資源的方式。缺點是佔用使用者更多的流量,優點是能夠實現真正意義上的頁面“秒開”。

客戶端離線包流程圖

附錄 D 優化後端介面資料

首屏動態渲染受制於後端介面返回的資料,如果介面存在體積大、有前後依賴關係、數量多需要耦合等問題,首屏渲染因為等待資料往往會比較慢。解決辦法是拉上後端一起梳理下哪些資料才是首屏所需要的,用一個介面把首屏資料輸送給前端。

附錄 E 優化佔用記憶體

在瀏覽器控制檯的 Performance 欄位,可以記錄整個頁面生命週期的每一個細節,其中有大量描述 JavaScript 堆疊記憶體佔用的情況。

Google Chrome Performance

E.1 CPU 記憶體

CPU memory is attached to the CPU, and is almost universally two DIMMs wide (128b), and is a multi-drop bus (so requires more power and conditioning to drive, even at lower clocks.) Of course, we generally expect to be able to configure CPU memory by snapping in different DIMMs, so the CPU’s memory controller is far more complicated and flexible.

JavaScript 對記憶體的佔用受程式碼的影響,如果在執行時快取和計算大量的資料、處理巨量字串等耗費空間的行為,那麼記憶體就會極速飆升,極端情況下會導致承載網頁的應用閃退。

E.2 GPU 視訊記憶體

GPU memory is attached to the GPU, and is a wider interface, with shorter paths and a point-to-point connection. As a consequence, it generally runs at higher speed (clock) than CPU memory.
It’s common for GPU memory to deliver several hundred GB/s to the GPU; for a CPU, it’s in the mid tens of GB/s. (There are higher-end CPUs with very wide interfaces that are around 100 GB/s.)
The internal design of both kinds of memory is very similar.

GPU memory

經由我自測,這部分記憶體受螢幕尺寸和幀數影響較大,如果是動畫或高精度的圖片渲染時,則記憶體會向上浮動。

附錄 F 預渲染

動態渲染的頁面,首屏需要等待 JavaScript 載入完成之後才能執行渲染,等待 JavaScript 載入的時間越久,白屏的時間越久。而通過在 CI/CD 階段,將傳統 SSR 的流程執行一遍,用動態生成的 index.html 覆蓋原來“空的”index.html,即優化了首屏載入體驗,省去了骨架屏的步驟,也提升了載入速度。使用 prerender-spa-plugin 可以輕鬆配置預渲染頁面,現已經被 React/Vue 專案廣泛應用。

參考

  1. Resource Hints – What is Preload, Prefetch, and Preconnect?
  2. 漸進式 Web 應用(PWA) | MDN
  3. What is the difference between GPU memory and CPU memory?
  4. 使用記憶體效能分析器檢視應用的記憶體使用情況

版權宣告

本部落格所有的原創文章,作者皆保留版權。轉載必須包含本宣告,保持本文完整,並以超連結形式註明作者後除和本文原始地址:https://blog.mazey.net/2548.html

(完)

相關文章