前端性能和加載體驗優化實踐(附:PWA、離線包、內存優化、預渲染)

前端性能和加載體驗優化實踐

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

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

(完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章