前言
移動網際網路發展至今,各種移動裝置應運而生,但它們的物理解析度可以說是五花八門,一般情況UI會為我們提供375尺寸的設計稿,所以為了讓H5頁面能夠在這些不同的裝置上儘量表現的一致,前端工程師就不得不對頁面進行移動端適配了。
「如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖
第一時間獲取最新文章~」
前置知識
在學習移動端適配前我們需要了解一些相關的前置知識。
螢幕尺寸
螢幕尺寸指的是以螢幕對角線的長度來計算的,單位是英寸。
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以上被歸為超高密度
裝置畫素比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="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>
不過上面這種方案是一種過渡方案,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="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>
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,三種方案表現出來基本一致。
總結
目前來講這三種方案是現在用的最多的方案,它們都有各自的優缺點。
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
我是南玖,我們下期見!!!