最近在重構公司的一個移動端專案,除了需要對新專案進行前端技術棧的搭建外,還需要考慮的一個重要問題就是移動端適配,關於移動端適配的理解我之前一直是處於一種比較朦朧的狀態(知其然而不知其所以然),所以最近又做了進一步的學習,在該博文中談談我對移動端適配的理解。
在這篇博文中,我會先對移動端裝置的一些基礎概念做一些解釋,包括裝置獨立畫素、裝置畫素比和 viewport ,然後在此基礎上對移動端適配做深入的說明,最後是探究移動端適配的解決方案。
裝置獨立畫素(dip)、裝置畫素比(dpr)
首先讓我們來了解一下關於移動裝置中的各個概念:
- 裝置畫素:即物理畫素,指裝置能控制顯示的最小單位,就是螢幕上一個個的畫素點。
- 螢幕尺寸:指螢幕的對角線長度,單位是英寸,1英寸=2.54釐米。
- 螢幕解析度:指手機螢幕的物理畫素點數,一般以「縱向物理畫素點數*橫向物理畫素點數」表示。如 iphone 6 的螢幕解析度為 1334 * 750。
- 螢幕畫素密度(dpi/ppi):指手機螢幕上每英寸物理畫素點數。其值與螢幕解析度和螢幕尺寸有關,計算公式是:dpi = √(縱向物理畫素點數² + 橫向物理畫素點數²) / 螢幕尺寸。
裝置獨立畫素(dip)也稱為邏輯畫素、密度獨立畫素,指獨立於裝置的用於邏輯上衡量畫素的單位。
裝置畫素比(dpr)指的是物理畫素與裝置獨立畫素的比例。在程式中則可以通過 window.devicePixelRatio 來獲取,該屬性是隻讀的,但不是常量,對瀏覽器的一些操作會改變這個值。
那麼各個移動裝置的裝置畫素比是怎麼得出來的呢?
裝置畫素比是跟該移動裝置的螢幕畫素密度有關的。一般來講,裝置畫素比是螢幕畫素密度除以 160 的整數倍,即 dpr = Math.floor(dpi / 160) = Math.floor(√(縱向物理畫素點數²+橫向物理畫素點數²) / 螢幕尺寸 / 160) 。
如 iphone 6 尺寸為 4.7 英寸,螢幕解析度為 1334 * 750,那麼我們可以得出 iphone 6 的裝置畫素比為:Math.floor(√(1334²+750²) / 4.7/160) = 2。
前面說到,裝置畫素比指的是物理畫素與裝置獨立畫素的比例,所以在知道了移動裝置的裝置畫素比之後,我們便可以得出該移動裝置的裝置獨立畫素,即裝置獨立畫素 = 物理畫素 / 裝置畫素比。
如 iphone 6 的橫向解析度是 750,裝置畫素比是 2,那麼可以得出 iphone 6 的邏輯寬度(即以裝置獨立畫素來計算的移動裝置的寬度)是 375px 。
viewport
viewprot 指的是移動裝置瀏覽器中放置頁面的一個虛擬的視窗,該視窗可大於或小於移動裝置的可視區域。
一般移動裝置預設都是 viewprot 大於其可視區域,這樣不會破壞沒有針對移動裝置優化的網頁的佈局,使用者可以通過平移和縮放來看網頁的其他部分,大部分移動裝置預設的 viewport 為 980px(這裡的 px 指的就是裝置獨立畫素)。
我們在進行移動端頁面的重構時,經常會加上如下一句程式碼:
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
複製程式碼
這句程式碼的作用是將移動裝置的 viewport 設定為該裝置的邏輯寬度,同時初始縮放值為 1 最大縮放值為1,並且不允許使用者進行縮放。
讓我們來看一下 viewport 中的屬性:
當我們把 viewport 中的 width 設定為 width-device 時,viewport 的寬度等於移動裝置的邏輯寬度,而如果設定 initial-scale 為 1 也會得到相同結果。一般來講我們會同時設定這兩個屬性,當這兩個屬性因為值不同而產生不同的效果時,瀏覽器則會取得兩者中較大的值。
下面舉個例子來幫助理解 viewport 屬性的作用:
<style>
html,body{
margin:0;
padding:0;
}
.box{
width:100px;
height:100px;
background:blue;
}
</style>
<div class="box"></div>
複製程式碼
在谷歌瀏覽器中以 iphone 6 的除錯效果如下:
可以看到,在沒有加上 viewport 屬性的情況下,iphone 6 中 viewport 的預設值是 980px,前面說過,這個是以裝置獨立畫素來計算的,也就是和我們平時在樣式程式碼中寫的 px 是一樣的,程式碼中定義了一個寬高都為 100px 的藍色塊,所以從橫向上來看的話,藍色塊在螢幕中佔了 100/980,如圖所示。
而如果我們設定了 viewport 屬性的話:
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
複製程式碼
效果如下:
可以看到,在加上了 viewport 屬性後,移動裝置的 viewport 寬度被設定成了 device-width,即該移動裝置的邏輯寬度 ,在 iphone 6 中 device-width 值為 375px,所以從橫向上來看的話,藍色塊在螢幕中佔了 100/375,如圖所示。
深入理解移動端適配
前面我們已經理解了裝置獨立畫素、裝置畫素比和 viewport 的一些概念,那麼要深入地去理解移動端適配,我們需要先弄清楚裝置畫素比的作用是什麼。
舉一個最容易理解的例子。iphone 3 和 iphone 4 的螢幕尺寸是一樣的,但是 iphone 3 的螢幕解析度是 480 * 320,而 iphone 4 的螢幕解析度是 960 * 640,iphone 4 的橫縱向解析度分別是 iphone 3 橫縱向解析度的兩倍。
假設 iphone 3 和 iphone 4 的裝置畫素比都是 1 的話,那麼可以得到它們的邏輯寬度分別是 320px 和 640px,從上面加了 viewport 屬性的例子來看的話,在 iphone 3 中藍色塊的寬度佔比是 100/320,而在 iphone 4 中佔比是 100/640,我們就會看到同樣的程式碼在不同的移動裝置中的顯示差別很大。
而實際上 iphone 3 的裝置畫素比是 1,iphone 4 的裝置畫素比是 2,所以我們可以得到它們的邏輯寬度都是 320px,所以實際上藍色塊在這兩款不同的移動裝置中顯示的大小是一樣的。
所以我們可以得出,使用 px 畫素單位時,同樣樣式的程式碼在不同螢幕解析度的移動裝置中顯示的情況基本一致(注意這裡是基本一致而不是完全一致),是因為移動裝置中的裝置畫素比將裝置獨立畫素轉換了。
而為什麼說在不同解析度的移動裝置中顯示的情況基本一致而不會完全一致呢?是因為存在很多不同螢幕解析度的移動裝置,它們的邏輯寬度並不都是一樣的。
如 iphone 6 的解析度是 1334 * 750,裝置畫素比是 2,所以其邏輯寬度是 375px,從上面加了 viewport 屬性的例子來看的話,藍色塊佔比是 100/375,可以看到其藍色塊在螢幕中的佔比跟 iphone 3、iphone 4 是不一樣的。
所以我們可以知道,裝置畫素比在一定程度上幫助我們進行了移動端適配。實際上,我們還需要通過一些其它方案來對移動端進行適配。我們需要進行移動端適配的根本原因是存在多種不同邏輯寬度的移動裝置
我們對移動端進行適配的一個終極目標就是,使其在不同移動裝置中所佔的比例是大致相同(對於上圖例子來說,就是使藍色塊在不同的移動裝置中所佔的比例差不多)。
移動端適配解決方案
最後,是探究移動端適配的解決方案,這裡我要為 《從網易與淘寶的font-size思考前端設計稿與工作流》 這篇博文打 call,寫得真的很不錯。在這篇博文中,分別介紹了拉勾、網易和淘寶的移動端適配解決方案,拉勾是通過設定樣式的百分比來達到在一定程度上對不同解析度的移動裝置進行適配,這種方式適合簡單專案的適配;而網易和淘寶使用的則是眾所周知的方案,使用 rem 進行適配。
相對而言,我覺得網易的方案更加容易理解和實踐,所以在我的專案中也是參考網易的方案來實現移動端適配的。而對於淘寶的移動端適配方案,則顯得更加精巧,感興趣的童鞋可以自行閱讀理解哦。
那麼,rem 是什麼呢?rem(font size of the root element),意思即根據根元素的 font-size 來設定字型的大小。跟 px 一樣,它是 CSS 中的一個樣式單位,會根據根元素的 font-size 值來轉換成 px 單位,公式為:px = rem * html(font-size)。
html{
font-size:10px;
}
div{
width:2rem; // 2*10=20px
}
複製程式碼
所以我們實現移動端適配的核心思想就是:使用 rem 作為樣式單位,根據不同解析度的移動裝置設定根元素的 font-size 值。
那麼問題來了,如何合理地設定根元素的 font-size 值呢?
以 iphone 6 的設計稿為基準,即設計稿橫向解析度為 750,取 100 為參照數(即在使用 rem 時與使用 px 時相差 100 的倍數),則我們可以知道 html 的寬度為 7.5rem(750 / 100),而我們知道 iphone 6 的邏輯寬度是 375px,所以 html 的寬度也為 375px,那麼此時 7.5rem * html(font-size) = 375px,所以可以得出 html(font-size) = 375 / 7.5,即 html(font-size) = deviceWidth / 7.5。
通過 js 來設定根元素的 font-size
var deviceWidth = document.documentElement.clientWidth;
document.documentElement.style.fontSize = deviceWidth / 7.5 + 'px';
複製程式碼
當然,這裡有一個前提,就是設定 viewprot 寬度為移動裝置的邏輯寬度
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
複製程式碼
而當 deviceWidth 大於 750px 時,我們應該去訪問的是 pc 版的頁面,所以當 deviceWidth 大於 750px 時我們不應該再改變根元素的 font-size 值,完整的程式碼如下
var deviceWidth = document.documentElement.clientWidth;
if(deviceWidth > 750) deviceWidth = 750;
document.documentElement.style.fontSize = deviceWidth / 7.5 + 'px';
複製程式碼
而為了使我們在書寫樣式的時候跟設計稿的大小更加契合,可以通過 sass 的 function 來設定一個 px 與 rem 之間的轉換函式
@function pxToRem($num) {
@return ($num/100) * 1rem;
}
複製程式碼
當設計稿中有一個寬高都為 100px 的元素時,我們便可以如下寫樣式
div{
width:pxToRem(100);
height:pxToRem(100);
}
複製程式碼
還需要注意的是為了使字型在不同解析度的移動裝置中看起來更加舒適,其大小不應該用 rem,而應該使用 px。
最後,總結一下我在專案中使用的移動端適配方案:
- 將 viewport 寬度設定為移動裝置邏輯寬度;
- 使用 js 根據不同解析度的移動裝置來設定根元素的 font-size 值,注意移動端與 pc 端的臨界值;
- 在樣式中字型使用 px 單位,而其它元素使用 rem 單位;
- 使用 sass 中的 function 來設定一個 px 與 rem 之間的轉換函式;
不定時分享個人在前端方面的學習經驗,覺得還不錯的小夥伴,可以關注一波公眾號哦。