移動端法門:自適應方案和高清方案

山頭人漢波發表於2022-04-21

筆者從畢業開始做前端到現在,90% 的專案是移動端打交道,所以當簡歷上寫了“移動H5”幾個字時,必會被問到自適應方案與高清方案

”自適應“講的是一套UI(例如750*1334),在多端下展示近乎一樣的效果;而”高清“是因為 DPR 提升而所做的各種精度適配

這篇文章講講筆者理解的自適應方案和高清方案

先說結論

自適應方案

  • rem

    • 適配思路

      • 選擇一個尺寸作為設計和開發基準
      • 定義一套適配規則,自動適配剩餘的尺寸
      • 特殊適配效果給出設計效果
    • 屬於歷史產物,CSS 視窗單位未得到主流瀏覽器的支援
    • 原理

      • 根據視窗寬度動態調整根元素 html 的 font-size 的值
      • 把總寬度設定為 100 份,每一份被稱為一個單位 x,同時設定 1rem 單位為 10x
    • 缺點

      • 需要載入 js 指令碼,而且根據裝置的視窗寬度進行計算,影響效能
    • 影響力:從2015年出世至今,在 H5 適配領域佔據一定比例
    • 相關技術庫:flexiblepx2rem
  • vw

    • 適配思路(如上)
    • 原理

      • 利用 CSS 視窗的特性,總寬度為 100vw,每一份為一個單位 1vw,設定 1rem 單位為 10vw
    • 缺點

      • 因為是根據檢視的寬度計算,所以不適用平板和PC
    • 影響力:2018年出的方案,目前 H5 適配主流
    • 相關技術庫:postcss-px-to-viewport
  • px + calc + clamp

    • 適配思路

      • 根據 CSS 的新特性:css變數、calc()函式、clamp()、@container函式實現
    • 特點

      • 解決了rem、vw佈局的致命缺點:失去畫素的完美性,而且一旦螢幕低於或高於某個閾值,通常就會出現佈局的移動或文字內容的溢位
      • 大漠在2021年提出,最先進,但沒看到大廠使用(clamp函式瀏覽器支援率暫且不高),具體可以看看大漠的這篇:如何構建一個完美縮放的UI介面
    • 缺點

      • 因為方案先進,暫沒看到大廠使用

高清方案

  • 1 畫素問題的解決方案
  • 不同 DPR 下圖片的高清解決方案

綜上,自適應方案是解決各終端的適配問題,高清方案是解決Retina屏的細節處理

寫在前面

在說移動端適配方案之前先整明白一些技術概念

裝置獨立畫素

裝置獨立畫素(DIP)=== CSS 畫素 === 邏輯畫素,在 Chrome 中能直接看到 375* 667

chrome中檢視css畫素

當你看到裝置獨立畫素時,不要慌,它表示 CSS 畫素,而它的長寬就是在 Chrome 中所查到的。可這樣記憶,“裝置獨立畫素”,字數長,文縐縐就是 CSS 畫素,也是理論上人為給定的指標,也叫邏輯畫素

物理畫素

物理畫素可以理解為手機廠商在賣手機時宣傳的解析度,即物理畫素 = 解析度,它表示垂直和水平上所具有的畫素點數

也就是說裝置螢幕的水平方向上有 1920 畫素點,垂直方向有 1080 畫素點(假設螢幕解析度為1920*1080),即螢幕解析度表示物理畫素,它在出廠時就定下來,單位為 pt,1pt=0.376mm

手機解析度

物理畫素,又被稱為裝置畫素,即表示 裝置畫素 === 物理畫素。可這樣記憶,裝置在物理世界能測量的長度

DPR(Device Pixel Ratio)

而裝置畫素比(DPR)是什麼?

DPR = 裝置畫素 / 裝置獨立畫素,它通常與視網膜屏(Retina 屏)有關

以 iPhone7 為例子,iPhone7 的 DPR = iPhone7 物理畫素 / iPhone7 裝置獨立畫素 = 2

寬 1334 / 667 = 2

高 750 / 375 = 2

得到 iPhone7 的 DPR 為 2,也就是我們常說的視網膜螢幕,而這就是營銷術語,它就是因為技術的進步,使得一個 CSS 畫素塞入更多的物理畫素

營銷術語還有哪些:農夫山泉的大自然的搬運工、元氣森林的“気”

筆者是這麼記憶的:

  • CSS 畫素(裝置獨立畫素)就像一個容器,以前是一比一塞入,所以 DPR 為 1,後來技術發展進步了,一個容器中能塞入更多的真實畫素(物理畫素)
  • DPR = 裝置畫素 / 裝置獨立畫素
  • DPR = 物理畫素(真實)/ CSS 畫素(虛的)

在視網膜螢幕中,以 DPR = 2 為例,把 4(2x2)個物理畫素當一個 CSS 畫素使用,這樣讓螢幕看起來更加清晰(精緻),但是元素的大小(CSS畫素)本身不會改變

DPR對比

隨著硬體的發展,像 iPhone13 Pro 等手機的 DPR 已經為 3,未來 DPR 突破 4 不是問題

說回來,DPR 為 2 或 3 會有什麼問題?我們以 CSS 為最小單位來寫程式碼的,展示在螢幕上也是以 CSS 為最小單位來展示,也就是說在 DPR 為 2 時,我們想要模擬 1 單位物理畫素是做不到的(如果瀏覽器支援用 0.5px CSS 的話,可以模擬,但是DPR為 3 呢,用 0.333px?);又因為手機的裝置獨立畫素(CSS 畫素)固定,使用傳統靜態佈局(固定 px)時,會出現樣式的錯位

iPhone 5/SE: 320 * 568 DPR: 2

iPhone 6/7/8: 375 * 667 DPR: 2

iPhone 6/7/8 Plus: 414* 736 DPR: 3

iPhone X: 375 * 812 DPR: 3

所以我們要適配各終端的 CSS 畫素以及不同 DPR 下,出現的 1 畫素問題、圖片高清問題等。隨著技術的發展,前端們擺脫了 IE 的相容,同時陷入了各大手機品牌的相容沼澤

自適應方案

Rem 佈局——天下第二

簡介:rem 就是相對於根元素 html 的 font-size 來做計算

與 rem 相關聯的是 em:

em 作為 font-size 單位時,其代表父元素的字型大小,em 作為其它屬性單位時,代表自身字型大小

rem 作用於非根元素時,相對於根元素字型大小,rem 作用於根元素字型時,相對於其初始字型大小

本質:等比縮放,是通過 JavaScript 來模擬 vw 的特性

假設將螢幕寬度平均分為 100 份,每一份的寬度用 x 表示,x = 螢幕寬度 / 100,如果將 x 作為單位,x 前面的數值就代表螢幕寬度的百分比

p { width: 50x } /* 螢幕寬度的 50% */ 

如果想要頁面元素隨著螢幕寬度等比變化,我們就需要上面的 x,這個 x 就是 vw,但是 vw 是在瀏覽器支援後才大規模使用,在此之前,js + rem 可模擬這種效果

之前說了,rem 作用於非根元素時,相對於根元素字型大小,所以我們設定根元素單位後,非根元素使用 rem 做相對單位

html { font-size: 16px }
p { width: 2rem } /* 32px */

html { font-size: 32px }
p { width: 2rem } /* 64px */

問題來了,我們要獲取到一個動態的根元素 font-size,並以此變化各個元素大小

有趣的是,我司兩個專案目前的做法是通過媒體查詢設定根元素,分為四檔,預設16px

筆者對這種做法表示不理解,原開發人員說我們這套執行了6年,UI適配也沒人說什麼問題。這裡就有個疑問了,真的如他所說UI適配的很好嗎,”媒體查詢根元素+rem“也能適配好嗎?是否完美呢?

後續筆者也會在 demo 中展示這種做法

但是根元素的 font-size 怎麼變化,它不可能一直是 16px,在中大屏下還可以,但是在小屏下字型就太大了,所以它的大小也應該是動態獲取的。如何讓其動態化,就是上文所說,讓根元素的 font-size 大小恆等於螢幕寬度的 1/100

html { font-size: width / 100};

如何設定 html 的字型大小恆等於螢幕寬度的百分之一呢?可以通過 js 來設定,一般需在頁面 dom ready、resize 和螢幕旋轉中設定

document.documentElement.style.fontSize = document.documentElement.clientWidth / 100 + 'px';
flexible 原始碼就如以上思路寫的

我們設定了百分之一的寬度後,在寫 css 時,就需要利用 scss/less 等 css 處理器來對 css 編譯處理。假設給出的設計圖為 750 * 1334,其中一個元素寬度為 200 px,根據公式:

width: 200 / 750 * 100 = 26.67 rem

在 sass 中,需要設定設計圖寬度來做換算:

@use 'sass:math';

$width: 750px;

@function px2rem($px) {
  @return #{math.div($px, $width) * 100}rem;
}

上面編譯完後

div {width: 26.667rem}

在不同尺寸下,它的寬度不同

機型尺寸width
iPhone 5/SE320 * 568170 * 170
iPhone 6/7/8375 * 667200 * 200
iPhone 6/7/8 Plus414 * 736220.797 * 220.797
iPhone X375 * 812200 * 200

效果如下(特意說明:圖中演示的是引入 flexible 庫,它的根元素的 font-size 為螢幕的 1/10)

rem佈局

REM 佈局(flexible)demo

優點:rem 的相容效能低到 ios 4.1,android 2.1

缺點:

  • 等比放大(可以說優點也可以理解為缺點,不同場景下使用)

    • 使用者選擇大螢幕有幾個出發點,有些人想要更大的字型,更大的圖片,有些人想要更多的內容,並不想要更大的圖示
  • 字型大小不能使用 rem(一般使用媒體查詢控制 font-size 大小)
  • 在 PC 端瀏覽破相,一般設定一個最大寬度
var clientWidth = document.documentElement.clientWidth;
clientWidth = clientWidth < 780 ? clientWidth : 780;
document.documentElement.style.fontSize = clientWidth / 100 + 'px';
body {
    margin: auto;
    width: 100rem;
}
  • 如果使用者禁止 js 怎麼辦?

    • 新增 noscripe 標籤提示使用者
    • <noscript>開啟JavaScript,獲得更好的體驗</noscript>
    • 給 HTML 新增一個 預設字型大小

相關技術方案:flexible(amfe-flexible 或者 lib-flexible) + postcss-pxtorem

Viewport 佈局——天不生我VW,適配萬古如長夜

vw 是基於 Viewport 視窗的長度單位,這裡的視窗(Viewport) 指的是瀏覽器視覺化的區域,而這個可視區域是 window.innerWidth/window.innerHeight 的大小

根據 CSS Values and Units Module Level 4: vw 等於初始包含塊(html元素)寬度的1%,也就是

  • 1vw 等於 window.innerWidth 的數值的 1%
  • 1vh 等於 window.innerHeight 的數值的 1%

看圖理解

螢幕的寬高

在說 rem 佈局時,曾經舉過 x 的例子,x 就是 vw

/* rem 方案 */
html { font-size: width / 100}
div { width: 26.67rem }

/* vw 方案 */
div { width: 26.67vw }

vw 還可以和 rem 方案結合,這樣計算 html 字型大小就不需要 js 了

html { font-size: 1vw }
div {width: 26.67rem }

效果如下:

vw適配

vw 適配是 CSS 原生支援,而且目前相容性大多數手機是支援的,也不需要載入 js ,也不會因為 js引發效能問題

vw 確實看上去很不錯,但是也存在一些問題

  • 也沒能很好的解決 1px 邊框在高清屏下的顯示問題,需要自行處理
  • 由於 vw 方案是完全的等比縮放,在 PC 端上會破相(和 rem一樣)

相關技術方案:postcss-px-to-viewport

VW 佈局demo

px適配——一力降十會

不用 rem/vw,用傳統的響應式佈局也能在移動端佈局中使用,需要設計規範

使用css 變數適配(篇幅原因暫不詳細介紹,可直接看程式碼

使用場景:新聞、內容型的網站,不太適用 rem,因為大屏使用者想要看到更多的內容,如網易新聞、知乎、taptap

PX + CSS變數 demo

媒體查詢——可有我一席?

上文講到我司原先H5端採用媒體查詢的方式來做適配,筆者嘗試復刻了下,只能說大差不差,能看出媒體查詢想做成這件事,但還是心有餘而力不足

採用rem、vw、px等方法能實現非標準尺寸(375 667設計稿)下 header 的高度為 165.59px,而 media 因為大屏,將根font-size 設定為17px,結果 header 的高度成為 159.38px(17 9.375rem)

如下GIF所示:

媒體查詢佈局與其他佈局對比

所以說僅用媒體查詢還是差強人意

媒體查詢佈局demo

各種適配的對比

vw、rem 適配的本質都是等比例縮放,px 直接寫,孰優孰劣看自己

REM佈局VW佈局PX + css變數佈局
容器最小寬度支援不支援支援
容器最大寬度支援不支援支援
高清裝置1px邊框支援支援支援
容器固定縱橫比支援支援支援
優點1.老牌方案
2.支援高清裝置1px邊框時,可按以往方式直接寫
1.無需引入js
2. 天然支援,寫法規範
同VW
缺點1. 需要引入 js 設定 html 的font-size
2. 字型大小不能使用 rem
3. 在 PC 端瀏覽會破相,一般需設定最大寬度
1.在PC端會破相
2.不支援老舊手機
同VW

除此之外,還有搭配 vw 和rem 的方案

  • 給根元素大小設定隨著視窗變化而變化的vw單位,動態變化各元素大小
  • 限制根元素字型大小的最大最小值,配合body加上最大寬度和最小寬度
// rem 單位換算:定為 75px 只是方便運算,750px-75px、640-64px、1080px-108px,如此類推
$vm_fontsize: 75; // iPhone 6尺寸的根元素大小基準值
@function rem($px) {
     @return ($px / $vm_fontsize ) * 1rem;
}
// 根元素大小使用 vw 單位
$vm_design: 750;
html {
    font-size: ($vm_fontsize / ($vm_design / 2)) * 100vw; 
    // 同時,通過Media Queries 限制根元素最大最小值
    @media screen and (max-width: 320px) {
        font-size: 64px;
    }
    @media screen and (min-width: 540px) {
        font-size: 108px;
    }
}
// body 也增加最大最小寬度限制,避免預設100%寬度的 block 元素跟隨 body 而過大過小
body {
    max-width: 540px;
    min-width: 320px;
}

高清方案

1畫素問題

1畫素指在 Retina 屏顯示 1單位物理畫素

很好理解,CSS 畫素(裝置獨立畫素)是我們人為規定的,當 DPR 為 1 時,1畫素(指我們寫的 CSS 畫素) 等於 1物理畫素;但當 DPR 為 3 時,1畫素就為 3 物理畫素

  • DPR = 1,此時 1 物理畫素 等於 1 CSS 畫素
  • DPR = 2,此時 1 物理畫素等於 0.5 CSS 畫素

    • border-width: 1px,這裡的 1px 其實是 1 CSS 畫素寬度,等於 2 物理畫素,設計師其實想要的是 border-width: 0.5px
  • DPR = 3,此時 1 物理畫素等於 0.33 CSS 畫素

    • 設計師想要的是 border-width: 0.33px

1畫素問題

解決思路

使用 0.5px 。有侷限性,iOS 8及以上,蘋果系統支援,但是 iOS 8以下和 Android(部分低端機),會將0.5px 顯示為 0px

既然 1 個 CSS 畫素代表 2(DPR 為2)、3(DPR為3)物理畫素,裝置又不認識 0.5px 的寫法,那就畫 1px,然後想辦法將寬度減少一半

方案

  • 漸變實現

    • background-image: linear-gradient(to top, ,,,)
  • 使用縮放實現

    • transform: scaleY(0.333)
  • 使用圖片實現

    • base64
  • 使用 SVG 實現

    • 嵌入 background url
  • border-image

    • 低端機下支援度不好

以上都是通過 CSS 的媒體查詢來實現的

@media only screen and (-webkit-min-device-pixel-ratio: 2),
    only screen and (min-device-pixel-ratio: 2) {}
@media only screen and (-webkit-min-device-pixel-ratio: 3),
    only screen and (min-device-pixel-ratio: 3) {
        
}

圖片適配和優化

影像通常佔據了網頁上下載資源絕大部分,優化影像通常可以最大限度地減少從網站下載的位元組數以及提高網站效能

通常可以,有一些通用的優化手段:為不同 DPR 螢幕提供最適合的圖片尺寸

各大廠商的適配分析

看了不少文章,類似如:大廠是怎麼做移動端適配的

各大廠,有用rem適配的、也有用vm適配的、也有vm+rem結合適配的,純用 px 方案的也有

  • 新聞、社群等可閱讀內容較多的場景:px+flex+百分比

    • 如攜程、知乎、TapTap
  • 對視覺元件種類較多,依賴性較強的移動端頁面:vw+rem

    • 如電商、論壇

總結

rem 方案,引入 amfe-flexible

設計:設計出圖是 750 * 1334,設計切好圖後,上傳藍湖,按照尺寸寫 px。

開發:

  • 使用 rem 方案

    • 引入 amfe-flexible
    • 安裝 px2rem 之類的 px 轉 rem 工具
    • 配置 px2rem
    • 在專案中寫 px ,輸出時是 rem
    • 適用任何場景
  • 使用 vw 方案

    • 安裝 px2vw 之類的 px 轉 vw 工具
    • 配置 px2vw
    • 在專案中寫 px,輸出時是 vw
    • 適用任何場景
  • 使用 px 方案

    • 該怎麼樣就怎麼寫,不過因為有設計規劃,按鈕的大中小尺寸固定、icon 的尺寸有標準、TabBar 的高度也是寫死的,當一切都有標準後,寫頁面就方便了
    • 例如

      • 左邊固定 100 * 50,右邊 flex 佈局
      • 左邊固定 100 * 50,右邊 calc(100% - 100px)(使用 CSS3 中的 calc 計算)

其他

caniuse 網站測試CSS屬性與瀏覽器的相容性問題

疑問

Q:為什麼 H5 移動端UI庫單位大都是用 px?這樣不會有適配問題嗎?

其實我們寫好 px 後,如果專案採用 rem 寫業務,引入 px2rem(已經六年沒有維護了) 即可轉換。

在有贊 vant 庫中,它對瀏覽器適配的介紹是:

Viewport 佈局

Vant 預設使用 px 作為樣式單位,如果需要使用 viewport 單位(vw、vh、vmin、vmax),推薦使用 postcss-px-to-viewport 進行轉換

postcss-px-to-viewport 是一款 PostCSS 外掛,用於將 px 單位轉化為 vw/vh 單位

Rem 佈局

如果需要使用 rem 單位進行適配,推薦使用以下兩個工具:

  • postcss-pxtorem 是一款 PostCSS 外掛,用於將 px 單位轉化為 rem 單位
  • lib-flexible 用於設定 rem 基準值

demo 合集:線上demo

參考資料

相關文章