超詳細講解H5移動端適配
前言
移動互聯網發展至今,各種移動設備應運而生,但它們的物理分辨率可以說是五花八門,一般情況UI會為我們提供375尺寸的設計稿,所以為了讓H5頁面能夠在這些不同的設備上盡量表現的一致,前端工程師就不得不對頁面進行移動端適配了。
「如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖
第一時間獲取最新文章~」
前置知識
在學習移動端適配前我們需要了解一些相關的前置知識。
屏幕尺寸
屏幕尺寸指的是以屏幕對角線的長度來計算的,單位是英寸。
1英寸=2.54厘米
電子設備一般都用英寸來描述屏幕的物理大小,比如我們電腦常見的22、27英寸。英寸(inch,縮寫為in)在荷蘭語中的意思指的是大拇指,一英寸就是指普通人的拇指寬度。
像素pixel
從計算機技術的角度來解釋,像素是硬件和軟件所能控制的最小單位。它指顯示屏的畫面上表示出來的最小單位,不是圖畫上的最小單位。一幅圖像通常包含成千上萬個像素,每個像素都有自己的顏色信息,它們緊密地組合在一起。一個像素,就是一個點,或者說是一個很小的正方形。
屏幕分辨率
屏幕分辨率指一個屏幕具體由多少個像素點組成,單位是px。
我們可以看到上圖中有兩種像素:邏輯像素與物理像素,並且它們數值不一樣,還有就是為什麼一般UI給我們提供的設計稿上的分辨率與真實機型的分辨率不一樣,。
物理像素(設備像素)
在同一個設備上,他的物理像素是固定的,也就是廠家在生產顯示設備時就決定的實際點的個數,對於不同設備物理像素點的大小是不一樣的。(設備控制顯示的最小單位,我們常說的1920*1080像素分辨素就是用的物理像素單位)
如果都使用物理像素就會帶來問題:舉個例子,21英寸顯示器的分辨率是1440×1080,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以上被歸為超高密度
設備像素比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像素。
概念關係圖
屏幕尺寸、屏幕分辨率–>對角線分辨率/屏幕尺寸–>屏幕像素密度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)
它指的是瀏覽器的可視區域,也就是我們在移動端設備上能夠看到的區域。默認與當前瀏覽器窗口大小相等,當用戶對瀏覽器進行縮放時,不會改變布局視口的大小,但會改變視覺窗口的大小。
我們可以通過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-width
,initial-scale=2
,此時視覺視口寬度 = 理想視口寬度 / initial-scale
即200px
,布局視口取兩者最大值即device-width
400px
。
若設置width=device-width
,initial-scale=0.5
,此時視覺視口寬度 = 理想視口寬度 / initial-scale
即800px
,布局視口取兩者最大值即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>
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="//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>
不過上面這種方案是一種過渡方案,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
:vw
和vh
中的較小值vmax
: 選取vw
和vh
中的較大值
如果按視覺視口為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="//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>
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="//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,三種方案表現出來基本一致。
總結
目前來講這三種方案是現在用的最多的方案,它們都有各自的優缺點。
rem方案
- 適配原理稍複雜
- 需要使用 JS
- 設計稿標註的 px 換算到 css 的 rem 計算簡單
- 方案靈活,既能實現整體縮放,又能實現局部不縮放
vw 方案
- 適配原理簡單
- 不需要 JS 即可適配
- 設計稿標註的 px 換算到 css 的 vw 計算複雜
- 方案靈活,既能實現整體縮放,又能實現局部不縮放
viewport+px方案
- 適配原理簡單
- 需要使用 JS
- 直接使用設計稿標註無需換算
- 方案死板,只能實現頁面級別肢體縮放
推薦閱讀
- 性能優化之html、css、js三者的加載順序
- HTTP發展史,HTTP1.1與HTTP2.0的區別
- 超全面總結Vue面試知識點,助力金三銀四
- 【面試必備】前端常見的排序算法
- CSS性能優化的幾個技巧
- 前端常見的安全問題及防範措施
- 為什麼大廠前端監控都在用GIF做埋點?
- 前端人員不要只知道KFC,你應該了解 BFC、IFC、GFC 和 FFC
我是南玖,我們下期見!!!