超詳細講解H5移動端適配

前端南玖發表於2022-04-13

前言

移動網際網路發展至今,各種移動裝置應運而生,但它們的物理解析度可以說是五花八門,一般情況UI會為我們提供375尺寸的設計稿,所以為了讓H5頁面能夠在這些不同的裝置上儘量表現的一致,前端工程師就不得不對頁面進行移動端適配了。

「如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~」

前置知識

在學習移動端適配前我們需要了解一些相關的前置知識。

1.png

螢幕尺寸

螢幕尺寸指的是以螢幕對角線的長度來計算的,單位是英寸。1英寸=2.54釐米

電子裝置一般都用英寸來描述螢幕的物理大小,比如我們電腦常見的22、27英寸。英寸(inch,縮寫為in)在荷蘭語中的意思指的是大拇指,一英寸就是指普通人的拇指寬度。

畫素pixel

從計算機技術的角度來解釋,畫素是硬體和軟體所能控制的最小單位。它指螢幕的畫面上表示出來的最小單位,不是圖畫上的最小單位。一幅影像通常包含成千上萬個畫素,每個畫素都有自己的顏色資訊,它們緊密地組合在一起。一個畫素,就是一個點,或者說是一個很小的正方形

螢幕解析度

螢幕解析度指一個螢幕具體由多少個畫素點組成,單位是px。

我們可以看到上圖中有兩種畫素:邏輯畫素與物理畫素,並且它們數值不一樣,還有就是為什麼一般UI給我們提供的設計稿上的解析度與真實機型的解析度不一樣,。

物理畫素(裝置畫素)

在同一個裝置上,他的物理畫素是固定的,也就是廠家在生產顯示裝置時就決定的實際點的個數,對於不同裝置物理畫素點的大小是不一樣的。(裝置控制顯示的最小單位,我們常說的1920*1080畫素分辨素就是用的物理畫素單位)

如果都使用物理畫素就會帶來問題:舉個例子,21英寸顯示器的解析度是1440x1080,5.8英寸的iPhone X的解析度是2436×1125,我們用CSS畫一條線,其長度是20px,如果都以物理畫素作為度量單位,那麼在顯示器上看起來正常,在iPhone X螢幕上就變得非常小。

邏輯畫素(裝置獨立畫素)

OK,其實喬幫主在之前就想到了會有這個問題,蘋果在iPhone4的釋出會上首次提出了Retina Display(視網膜螢幕)的概念,在iPhone4使用的視網膜螢幕中,把4個畫素當1個畫素使用,這樣讓螢幕看起來更精緻,並且在不同螢幕中,相同的邏輯畫素呈現的尺寸是一致的。所以高解析度的裝置,多了一個邏輯畫素。我們從第一張圖中可以看到不同裝置的邏輯畫素仍然是有差異的,只不過差異沒有物理畫素那麼大,於是便誕生了移動端頁面需要適配這個問題。(與裝置無關的邏輯畫素,代表可以通過程式控制使用的虛擬畫素)

每英寸畫素點ppi

ppi(pixel per inch) 表示每英寸所包含的畫素點數目,數值越高,說明螢幕能以更高密度顯示影像。

它的計算公式為:PPI=√(X^2+Y^2)/ Z (X:長度畫素數;Y:寬度畫素數;Z:螢幕大小)

ppi在120-160之間的手機被歸為低密度手機,160-240被歸為中密度,240-320被歸為高密度,320以上被歸為超高密度

3.png

裝置畫素比dpr

dpr(device pixel ratio) 表示裝置畫素比,裝置畫素/裝置獨立畫素,代表裝置獨立畫素到裝置畫素的轉換關係,在JS中可以通過 window.devicePixelRatio 獲取

計算公式為:DPR = 物理畫素/邏輯畫素

當裝置畫素比為1:1時,使用1(1×1)個裝置畫素顯示1個CSS畫素;

當裝置畫素比為2:1時,使用4(2×2)個裝置畫素顯示1個CSS畫素;

當裝置畫素比為3:1時,使用9(3×3)個裝置畫素顯示1個CSS畫素。

2.png

概念關係圖

螢幕尺寸、螢幕解析度-->對角線解析度/螢幕尺寸-->螢幕畫素密度PPI | 裝置畫素比dpr = 物理畫素 / 裝置獨立畫素dip(dp) | viewport: scale | CSS畫素px

視口viewport

viewport指的是視口,他是瀏覽器或app中webview顯示頁面的區域。一般來講PC端的視口指的是瀏覽器視窗區域,而移動端就有點複雜,它有三個視口:

  • layout viewport:佈局視口
  • visual viewport:視覺視口
  • ideal viewport:理想視口

佈局視口(layout viewport)

它是由瀏覽器提出的一種虛擬的佈局視口,用來解決頁面在手機上顯示的問題。這種視口可以通過<meta>標籤設定viewport來改變。移動裝置上的瀏覽器都會把自己預設的viewport設為980px或1024px(也可能是其它值,這個是由裝置自己決定的),但帶來的後果就是瀏覽器會出現橫向滾動條,因為瀏覽器可視區域的寬度是比這個預設的viewport的寬度要小的。

我們可以通過document.documentElement.clientWidth來獲取佈局視口大小

視覺視口(visual viewport)

它指的是瀏覽器的可視區域,也就是我們在移動端裝置上能夠看到的區域。預設與當前瀏覽器視窗大小相等,當使用者對瀏覽器進行縮放時,不會改變佈局視口的大小,但會改變視覺視窗的大小。

5.png

我們可以通過window.innerWidth來獲取視覺視口大小。

理想視口(ideal viewport)

理想中的視口。這個概念最早由蘋果提出,其他瀏覽器廠商陸續跟進,目的是解決在佈局視口下頁面元素過小的問題,顯示在理想視口中的頁面具有最理想的寬度,使用者無需進行縮放。所謂理想視口,即頁面繪製區域可以完美適配裝置寬度的視口大小,不需要出現滾動條即可正常檢視網站的所有內容,且文字圖片清晰,如所有iphone的理想視口寬度都為320px,安卓裝置的理想視口有320px、360px等等。

當頁面縮放比例為100%時,理想視口 = 視覺視口

我們可以通過screen.width來獲取理想視口大小。

meta viewport

對於移動端頁面,可以採用<meta>標籤來配置視口大小和縮放等。

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  • width:該屬性被用來控制視窗的寬度,可以將width設定為320這樣確切的畫素數,也可以設為device-width這樣的關鍵字,表示裝置的實際寬度,一般為了自適應佈局,普遍的做法是將width設定為device-width
  • height:該屬性被用來控制視窗的高度,可以將height設定為640這樣確切的畫素數,也可以設為device-height這樣的關鍵字,表示裝置的實際高度,一般不會設定視窗的高度,這樣內容超出的話採用滾動方式瀏覽。
  • initial-scale:該屬性用於指定頁面的初始縮放比例,可以配置0.0~10的數字,initial-scale=1表示不進行縮放,視窗剛好等於理想視窗,當大於1時表示將視窗進行放大,小於1時表示縮小。這裡只表示初始視窗縮放值,使用者也可以自己進行縮放,例如雙指拖動手勢縮放或者雙擊手勢放大。安卓裝置上的initial-scale預設值: 無預設值,一定要設定,這個屬性才會起作用。在iphone和ipad上,無論你給viewport設的寬的是多少,如果沒有指定預設的縮放值,則iphone和ipad會自動計算這個縮放值,以達到當前頁面不會出現橫向滾動條(或者說viewport的寬度就是螢幕的寬度)的目的。
  • maximum-scale:該屬性表示使用者能夠手動放大的最大比例,可以配置0.0~10的數字。
  • minimum-scale:該屬性類似maximum-scale,用來指定頁面縮小的最小比例。通常情況下,不會定義該屬性的值,頁面太小將難以瀏覽。
  • user-scalable:該屬性表示是否允許使用者手動進行縮放,可配置no或者yes。當配置成no時,使用者將不能通過手勢操作的方式對頁面進行縮放。

這裡需要注意的是viewport只對移動端瀏覽器有效,對PC端瀏覽器是無效的。

適配與縮放

為了讓移動端頁面獲得更好的顯示效果,我們必須讓佈局視口、視覺視口都儘可能等於理想視口,所以我們一般會設定width=device-width,這就相當於讓佈局視口等於理想視口;設定initial-scale=1.0,相當於讓視覺視口等於理想視口;

上面提到width可以決定佈局視口的寬度,實際上它並不是佈局視口的唯一決定性因素,設定initial-scale也有肯能影響到佈局視口,因為佈局視口寬度取的是width和視覺視口寬度的最大值。

例如:若手機的理想視口寬度為400px,設定width=device-widthinitial-scale=2,此時視覺視口寬度 = 理想視口寬度 / initial-scale200px,佈局視口取兩者最大值即device-width 400px

若設定width=device-widthinitial-scale=0.5,此時視覺視口寬度 = 理想視口寬度 / initial-scale800px,佈局視口取兩者最大值即800px

移動端適配方案

當我們在做H5移動端開發時,用到的最多的單位是PX,也就是CSS畫素,當頁面縮放比為1:1時`,一個CSS畫素等於一個裝置獨立畫素。但CSS畫素是很容易被改變的,比如使用者對頁面進行放大,CSS畫素會被放大,此時的CSS畫素會跨越更多的裝置畫素。

頁面縮放係數 = CSS畫素 / 裝置獨立畫素

rem適配

rem(font size of the root element)是CSS3新增的一個相對單位,是指相對於根元素的字型大小的單位。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
    <title>Document</title>
    <style>
        *{margin:0;padding:0}
        .box{
            width: 10rem;
            height: 4rem;
            background-color: antiquewhite;
            font-size: 0.53rem; /* 20px*/
        }
    </style>
    <script>
        function setRootRem() {
            const root = document.documentElement;
            /** 以iPhone6為例:佈局視口為375px,我們把它分成10份,則1rem = 37.5px,
             * 這時UI給定一個元素的寬為375px(裝置獨立畫素),
             * 我們只需要將它設定為375 / 37.5 = 10rem。
            */
            const scale = root.clientWidth / 10
            root.style.fontSize = scale + 'px'  
        }
        setRootRem()
        window.addEventListener('resize', setRootRem)
    </script>
</head>
<body>
    <div class="box">前端南玖</div>
</body>
</html>

rem.gif

Ok,這裡我們可以看到,我們在選用不同裝置進行測試時,根節點的的font-size會隨著裝置的佈局視口的寬度變化而變化,所以這裡的元素寬度10rem永遠都是等於當前佈局視口的寬度,font-size也會隨裝置變化而變化。這就是所謂的移動端適配,其實這種方案最早是由阿里提出來的一個開源移動端適配解決方案flexible,原理非常簡單。

但這樣我們會發現在寫佈局的時候會非常複雜,也就是你需要自己手動去計算一下對應的rem值,比如上面的font-size設計稿上是20px,那我們就要計算一下20px對應的rem是多少,按我們上面的規則,1px = 1/37.5rem,所以20px應該對應20/37.5 = 0.53rem。所以這種方案我們通常搭配著CSS前處理器使用,還不瞭解CSS前處理器的同學推薦看我之前的文章:談到CSS前處理器,你是不是隻會用巢狀?

rem搭配CSS前處理器使用

這裡我就用vue+less來簡單操作一下,具體可以封裝到底層,這裡暫且演示一下原理。

這裡推薦一下使用我的自制腳手架 (songyao-cli) 來快速生成一個vue專案,安裝完依賴後,開始配置less.

/*rem.less*/
@device-width: 375; /*裝置佈局視口*/
@rem: (@device-width/10rem);

然後將@rem配置成less全域性變數

//vue.config.js
module.exports = {
    css: {
        loaderOptions:{
            less: {
                additionalData: ` @import '~@/static/rem.less';`
            }
        }
    }
}

在vue的入口檔案配置計算rem的方法

// toRem.js
export default function() {
    const root = document.documentElement;
    /** 以iPhone6為例:佈局視口為375px,我們把它分成10份,則1rem = 37.5px,
     * 這時UI給定一個元素的寬為375px(裝置獨立畫素),
     * 我們只需要將它設定為375 / 37.5 = 10rem。
    */
    const scale = root.clientWidth / 10
    root.style.fontSize = scale + 'px'  
}


//main.js
import Vue from 'vue'
import App from './App.vue'
import toRem from "./utils/toRem" //
toRem()
window.addEventListener('resize', toRem)
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

然後就可以在vue中使用全域性變數@rem進行移動端開發了

<template>
    <div class="songyao">
        <h1>{{ username }}</h1>
    <p>
      瞭解腳手架及腳手架指令請移步個人部落格<br>
      check out the
      <a href="http://47.100.126.169/zmengBlog" target="_blank" rel="noopener">逐夢部落格</a>.
    </p>
    <p>微信公眾號:<span class="wx_name">前端南玖</span></p>
    </div>
</template>

<script>
export default {
    name: 'songyao',
    data() {
        return {
            username: 'songyao-cli(vue 模板)'
        }
    },
}
</script>

<style lang="less">
.songyao{
    h1{
        font-size: (24/@rem);
    }
    p{
        font-size: (16/@rem);
    }
   .wx_name{
    color:brown;
    }
}

</style>

rem2.gif

不過上面這種方案是一種過渡方案,viewport是由蘋果提出的一種方案,之前各大瀏覽器對其相容性並不是很好,這才有了這種rem適配方案。

在阿里開源庫flexible文件上有這麼一句話:

由於viewport單位得到眾多瀏覽器的相容,lib-flexible這個過渡方案已經可以放棄使用,不管是現在的版本還是以前的版本,都存有一定的問題。建議大家開始使用viewport來替代此方。

對,這種方案已經在慢慢被拋棄了,不過還有不少企業在用。

vw、vh適配

vw(Viewport Width)vh(Viewport Height)是基於檢視視窗的單位,是css3中提出來的,基於檢視視窗的單位。

vh、vw方案即將視覺視口寬度 window.innerWidth和視覺視口高度 window.innerHeight 等分為 100 份。

上面的flexible方案就是模仿這種方案,因為早些時候vw還沒有得到很好的相容。

  • vw(Viewport's width)1vw等於視覺視口的1%
  • vh(Viewport's height) :1vh 為視覺視口高度的1%
  • vmin : vwvh 中的較小值
  • vmax : 選取 vwvh 中的較大值

如果按視覺視口為375px,那麼1vw = 3.75px,這時UI給定一個元素的寬為75px(裝置獨立畫素),我們只需要將它設定為75 / 3.75 = 20vw

這裡我們同樣可以藉助less來實現,不用自己去手動算,算的過程我們交給less就好了,我們直接按照設計稿上去開發就行

// 還是rem.less 我們加一個@vw變數
@device-width: 375;
@rem: (@device-width/10rem);
@vw: (100vw/@device-width);
<template>
    <div class="songyao">
        <h1>{{ username }}</h1>
    <p>
      瞭解腳手架及腳手架指令請移步個人部落格<br>
      check out the
      <a href="http://47.100.126.169/zmengBlog" target="_blank" rel="noopener">逐夢部落格</a>.
    </p>
    <p>微信公眾號:<span class="wx_name">前端南玖</span></p>
    </div>
</template>

<script>
export default {
    name: 'songyao',
    data() {
        return {
            username: 'songyao-cli(vue 模板)'
        }
    },
}
</script>

<style lang="less">
.songyao{
    h1{
        // font-size: (24/@rem);
        font-size: 24*@vw;
    }
    p{
        // font-size: (16/@rem);
        font-size: 16*@vw;
    }
   .wx_name{
    color:brown;
    }
}

</style>

vw1.png

viewport+PX

OK,我們再來說一種flexible團隊推薦的viewport方案。這種方案可以讓我們在開發時不用關注裝置螢幕尺寸的差異,直接按照設計稿上的標註進行開發,也無需單位的換算,直接用px。

在 HTML 的 head 標籤里加入 <meta name="viewport" content="width={設計稿寬度}, initial-scale={螢幕邏輯畫素寬度/設計稿寬度}" >

假如UI給我們提供的設計稿寬度時375px,我們則需要將頁面的viewport的width設為375,然後再根據裝置的邏輯畫素將頁面進行整體放縮。

export function initViewport() {
    const width = 375;  // 設計稿寬度
    const scale = window.innerWidth / width
    // console.log('scale', scale)
    let meta = document.querySelector('meta[name=viewport]')
    let content = `width=${width}, init-scale=${scale}, user-scalable=no`
    if(!meta) {
        meta = document.createElement('meta')
        meta.setAttribute('name', 'viewport')
        document.head.appendChild(meta)
    }
    meta.setAttribute('content', content)
}
<template>
    <div class="songyao">
        <h1 class="name_rem">{{ username }}</h1>
        <h1 class="name_vw">{{ username }}</h1>
        <h1 class="name_px">{{ username }}</h1>
    <p>
      瞭解腳手架及腳手架指令請移步個人部落格<br>
      check out the
      <a href="http://47.100.126.169/zmengBlog" target="_blank" rel="noopener">逐夢部落格</a>.
    </p>
    <p>微信公眾號:<span class="wx_name">前端南玖</span></p>
    </div>
</template>

<script>
export default {
    name: 'songyao',
    data() {
        return {
            username: 'songyao-cli(vue 模板)'
        }
    },
}
</script>

<style lang="less">
.songyao{
    p{
        // font-size: (16/@rem);
        font-size: 16*@vw;
    }
    .name_rem{
        font-size: (24/@rem);
    }
    .name_vw{
        font-size: 24*@vw;
    }
    .name_px{
        font-size: 24px;
    }
   .wx_name{
    color:brown;
    }
}

</style>

這裡我們將三種方案放在一起對比一下,都是對應375設計稿上24px,三種方案表現出來基本一致。
px1.png

總結

目前來講這三種方案是現在用的最多的方案,它們都有各自的優缺點。

rem方案

  • 適配原理稍複雜
  • 需要使用 JS
  • 設計稿標註的 px 換算到 css 的 rem 計算簡單
  • 方案靈活,既能實現整體縮放,又能實現區域性不縮放

vw 方案

  • 適配原理簡單
  • 不需要 JS 即可適配
  • 設計稿標註的 px 換算到 css 的 vw 計算複雜
  • 方案靈活,既能實現整體縮放,又能實現區域性不縮放

viewport+px方案

  • 適配原理簡單
  • 需要使用 JS
  • 直接使用設計稿標註無需換算
  • 方案死板,只能實現頁面級別肢體縮放

推薦閱讀

原文首發地址點這裡,歡迎大家關注公眾號 「前端南玖」,如果你想進前端交流群一起學習,請點這裡

我是南玖,我們下期見!!!

相關文章