前言
移動網際網路發展至今,各種移動裝置應運而生,但它們的實體分辨率可以說是五花八門,一般情況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">eee</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預處理器使用
「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
- 直接使用設計稿标注無需換算
- 方案死闆,隻能實作頁面級别肢體縮放