移動開發屏幕適配分析


我在開發前端的時候曾經會有幾個疑惑:

1)拿到的設計搞的寬度是640px或750px的,在頁面不同尺寸屏幕布局的時候怎么換算。

2)移動端布局是用%、px、rem伸縮盒 Flexible Box Layout還是多列Multi-column

3)用px設置了字體大小,但是在ipad上面顯示的卻很小,字體大小是用rem比較好還是px+媒體查詢比較好。

4)媒體查詢@media分幾個尺寸的范圍能覆蓋比較多的尺寸情況。

在分析適配問題之前,要先了解一些基礎概念,Retina與HI-DPI、DPI與PPI、DP與PT、viewport等。

 

一、Retina顯示屏

Retina Display是蘋果注冊的命名方式,其他廠商只能使用HI-DPI或者其他的命名方式,不過意思都是一樣的,就是屏幕的PPI非常高。

iPhone 3GS(非Retina屏幕) iPhone 4(Retina屏幕)

右邊的圖片明顯要比左邊的清晰,這是因為PPI要高,何為PPI。

 

1)PPI與DPI

PPI和DPI這兩個是密度單位,不是度量單位。

1. PPI(pixels per inch):圖像分辨率 (在圖像中,每英寸所包含的像素數目)【1英寸(in)=2.54厘米(cm)】

2. DPI(dots per inch): 打印分辨率 (每英寸所能打印的點數,即打印精度)

PPI的計算方式如下,其中長度像素數和寬度像素數取的是物理像素(Physical Pixel)即設備像素

PPI = √(長度像素數² + 寬度像素數²) / 屏幕對角線英寸數

這個物理像素不是說屏幕的大小,例如iPhone4的屏幕是320*480(這是DP/PT設備獨立像素),但物理像素是640*960,屏幕是3.5寸。自己按下計算機算出來≈326。

知乎上看到,還有一種比較復雜的PPI計算方法,考慮了觀察者的距離,下圖是一個極簡版本:

上面又出現了幾個概念,物理像素、DP、PT又是什么鬼?

 

2)物理像素(Physical Pixel)

物理像素又被稱為設備像素,他是顯示設備中一個最微小的物理部件。下圖摘自《The Ultimate Guide To iPhone Resolutions》,原文是洋文寫的,對應的中文可以看這里

上圖中iPhone 3GS的物理元素就是320*480,而4/4S是640*960。其中4/4S的320*480其實就是PT,而render中的2x就是dpr(設備像素比)

 

3)DP、PT與SP

DP或PT是測量單位,DP(Density-independent pixels,也會叫DiP)表示獨立於設備的像素點,PT(Point)表示點。

DP用在Android上,PT用在Apple上,但是他們本質上是相同的。

SP(Scale-independent pixel)和DP、PT從用途上來講是不同的,但是工作方式相同。SP表示與比例無關的像素,通常用來定義字體大小。

這三個都是抽象像素,不會因為PPI的變化而變化,在相同物理尺寸和不同PPI下,他們呈現的高度大小是相同。也就是說更接近你手機看上去的大小,而px則不行。

上圖摘自google的《Device metrics》,要瀏覽google的頁面你懂得。表格中Density(密度)的基礎單位,在Android上是mdpi,而在IOS中是1x。

 

4)CSS像素

CSS像素是Web編程的概念,指的是CSS樣式代碼中使用的邏輯像素。例如width:300px跟font-size:14px,這類css語句中的px。

CSS像素和物理像素(Physical Pixel)是容納的關系。

例如iPhone4/4S中,1個CSS像素=4個物理像素(也叫設備像素):

做個簡單的例子,通過改變viewport,來手動放大屏幕。簡單設置一個ul的CSS。

ul{
    padding-left:20px;
    font-size: .5rem;
}

1. 頁面寬度等於屏幕DP或PT值

2. 頁面寬度等於物理像素(Physical Pixel)

從兩張圖片中可以看到,同樣是20px,看起來卻有大小差別,上圖比下圖寬了一倍,與第一張圖正好對應起來。

 

5)設備像素比(device pixel ratio)

設備像素比簡稱為dpr,其定義了物理像素(Physical Pixel)和DP/DiP(設備獨立像素)【IOS中是PT(點)】的對應關系。

設備像素比 = 物理像素(Physical Pixel) / 設備獨立像素(DP/DiP或PT)

DP/DiP或PT與屏幕密度有關(屏幕密度需通過PPI計算出來)。

在JavaScript中,可以通過 window.devicePixelRatio 獲取到當前設備的dpr。

而在CSS中,可以通過 -webkit-device-pixel-ratio , -webkit-min-device-pixel-ratio 和 -webkit-max-device-pixel-ratio 進行媒體查詢。

 

二、viewport

在桌面瀏覽器中,viewport就是瀏覽器窗口的寬度高度。

但移動設備的屏幕比桌面屏幕要小得多,為了要讓網頁在小尺寸的屏幕上顯示正確,就需要對viewport做些處理。

需要把viewport分成兩部分:visual viewport和layout viewport。

George Cummins在Stack Overflow上對這兩個概念做了分析。大致意思如下:

把layout viewport想像成為一張大圖。現在用一個比較小的框,通過它來看這張大圖。在框內看到的部分就是visual viewport。框中的度量單位是CSS像素。
可以把這個框靠近一些(放大看局部)或靠遠一些(縮小看整體)。也可以改變框的方向,但是大圖layout viewport的大小和形狀永遠不會變。

 

1)visual viewport

visual viewport是頁面當前顯示在屏幕上的部分。用戶可以通過滾動來改變他所看到的頁面的部分,或者通過縮放來改變visual viewport的大小。

結合上面的比喻,再想象一下大圖和框的操作。

圖片來自於文章《A tale of two viewports》,是用洋文寫的,對應的中文可以看這里

 

2)layout viewport

layout viewport就是頁面原來的大小。

但是我們用在手機用瀏覽器打開PC的網頁的時候,會看到網頁被瀏覽器自動縮小了,變的太小會導致無法瀏覽內容。

 

3)viewport meta標簽

為了不讓瀏覽器自動縮小,引入了viewport元標簽。通過這個元標簽控制layout viewport的寬度。

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">

上面這段HTML經常會在移動端頁面看到。

1. width:設置 layout viewport 的寬。

2. initial-scale:初始縮放比例,也即是當頁面第一次 load 的時候縮放比例,上面是變成設備的寬度,也就是layout viewport= DP或PT。

3. maximum-scale:允許用戶縮放到的最大比例。

4. minimum-scale:允許用戶縮放到的最小比例。

5. user-scalable:用戶是否可以手動縮放。

 
瀏覽器自動縮放 添加了viewport

注意下圖片中的紅色框,第二張圖片內容超了出來,應該是給文字設置了寬度,並且這個寬度大於layout viewport導致了出來。但其實在Android和IOS中會有不一樣的表現。

在文章《A tale of two viewports》中指出:

1. 通過 document.documentElement.clientWidth 獲取 layout viewport 的寬度

2. 通過 window.innerWidth 獲取 visual viewport 的寬度

我用iPhone6和紅米 note在自己本地做了個獲取這兩個值的測試,設置了個寬度,撐開頁面。

<div style="width: 700px">
  //文字內容
</div>

1. 紅米中的UC、微信瀏覽器、自帶的瀏覽器還有移動端的Chrome,顯示兩個值都一樣,是360px,只是UC和微信瀏覽器不會出現左右滾動條。

2. iPhone6中的UC瀏覽器和Safari顯示兩個值不一樣,layout viewport 的寬度是375px,而visual viewport 的寬度是708px,不會出現滾動條。

 

4)ideal viewport

這個viewport的尺寸,我理解相當於DP/DiP(設備獨立像素)或PT的尺寸。

在文章《Meta viewport》有詳細的說明,如果洋文看起來吃力,可以選擇瀏覽中文版的。

visual viewport寬度 = ideal viewport寬度/ zoom factor

zoom factor其實就是meta中的“initial-scale”。

設置initial-scale后,就開始相對於ideal viewport進行計算產生visual viewport,用visual viewport的寬度值去設置layout viewport的寬度值。

注意,剛剛用iPhone6測試獲取到layout viewport與visual viewport不同,一開始應該是相同的,只是后面渲染的時候style="width: 700px"把visual viewport給撐大了。

如果你拿到的設計稿是640px寬度的,在你用iPhone5布局的時候,其實你可以initial-scale=0.5,將layout viewport放大到640px,接下布局可以通過rem來設置。

但是手機屏幕的尺寸各不相同,那么initial-scale的值也應該是不同的,這個值是可以通過JavaScript計算出來的,可以參考flexible.js(移動自適應方案)中的代碼。

 

三、flexible.js

打開手機淘寶頁面,可以看到里面內聯了這個庫的代碼,這個flexible.js庫就是用來解決H5頁面終端適配的,具體的使用方法可以參考這里

1)rem

rem 就是相對於根元素 <html> 的 font-size 來做計算。flexible.js會給html賦font-size的值。下面的代碼是在iPhone5中模擬。

使用方法中並不推薦字體用rem單位,但我覺得可以因地制宜,具體情況具體分析,比如要在iPad中適配,這個時候用rem體驗會比較好。

 

2)計算initial-scale

注意下面的Android設備一律dpr=1,我后面做了個簡單的測試。

 1 var devicePixelRatio = win.devicePixelRatio;
 2 if (isIPhone) {
 3   // iOS下,對於2和3的屏,用2倍的方案,其余的用1倍方案
 4   if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
 5     dpr = 3;
 6   } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
 7     dpr = 2;
 8   } else {
 9     dpr = 1;
10   }
11 } else {
12   // 其他設備下,仍舊使用1倍的方案
13   dpr = 1;
14 }
15 scale = 1 / dpr;

將第2行修改下代碼,通過正則匹配來判斷dpr的值。

var isRegularDpr = devicePixelRatio.toString().match(/^[1-9]\d*$/g);
if (isRegularDpr) {
 //...邏輯與上面相同   
}

在IOS中表現正常,可是在Android中的UC、Chrome瀏覽器中會放大,微信瀏覽器顯示正常。順便說下,我的手機是紅米note,物理像素是720px,DP(設備獨立像素)是360px。

修改前 修改后  添加target-densitydpi=device-dpi
CSS像素是360px CSS像素是720px CSS像素是720px

第二和第三都是720px,寬度都變大了,只是一個是通過“initial-scale=0.5”,另一個是通過“target-densitydpi=device-dpi”。但字體一張變大一張變小了。

造成兩種展現不一樣的原因,我的理解是,第一種1個CSS像素=4個物理像素,而第二種是1個CSS像素=1個物理像素。

Android的viewport中屬性“target-densitydpi”,這個“DPI”就相當於“PPI”,我的紅米note的“PPI”是267。

可以的取值如下:

device-dpi: 使用設備原本的 dpi 作為目標 dp。 不會發生默認縮放。
high-dpi: 使用hdpi 作為目標 dpi。 中等像素密度和低像素密度設備相應縮小。
medium-dpi: 使用mdpi作為目標 dpi。 高像素密度設備相應放大, 像素密度設備相應縮小。 這是默認的target density.
low-dpi: 使用mdpi作為目標 dpi。中等像素密度和高像素密度設備相應放大。
<value>: 指定一個具體的dpi 值作為target dpi. 這個值的范圍必須在70–400之間。

有一篇文章《Viewport target-densitydpi support》曾經講到,這個屬性將被廢棄。

 

3)viewport元標簽

如果在頁面中已經設置了元標簽,那么就讀取元標簽中的內容,如果沒有設置,就動態添加meta標簽到head中。

 

4)refreshRem函數

此函數就是在設置html中font-size,以及flexible.rem與win.rem的值,flexible.rem會在后面的兩個輔助方法refreshRempx2rem內用到。

docEl.getBoundingClientRect().width是為了獲得 visual viewport 的寬度。

這個540其實是個經驗值,參考Issues#12,目前主流手機最大的css像素尺寸,是540(比如devicePixelRatio為2,分辨率是1080x1920的手機)。

width / dpr 是因為前面添加了viewport后,width會根據dpr(設備像素比)改變,而540定義的是一個DP/DiP(設備獨立像素)或PT值。

width / 10 主要為了以后能更好的兼容 vh 和 vw。

function refreshRem() {
  var width = docEl.getBoundingClientRect().width;
  if (width / dpr > 540) {
    width = 540 * dpr;
  }
  var rem = width / 10;
  docEl.style.fontSize = rem + 'px';
  flexible.rem = win.rem = rem;
}

 

5)pageshow與DOMContentLoaded

pageshow事件類似於 onload 事件,onload 事件在頁面第一次加載時觸發, onpageshow 事件在每次加載頁面時觸發,即 onload 事件在頁面從瀏覽器緩存中讀取時不觸發。

win.addEventListener('pageshow', function(e) {
  if (e.persisted) {
    clearTimeout(tid);
    tid = setTimeout(refreshRem, 300);
  }
}, false);

頁面文檔完全加載並解析完畢之后,會觸發DOMContentLoaded事件,HTML文檔不會等待樣式文件,圖片文件,子框架頁面的加載。

jQuery中的ready方法就是基於這個事件編寫的,查看ready源碼

 

現在我自己來解答一下自己的疑惑:

1)通過修改initial-scale的值,然后配合rem來做換算,可以參考flexible.js中計算方式。

2)這幾種布局應該是配合使用,因地制宜,我前面的一篇文章中就記錄使用了多列與伸縮盒的使用場景。

3)還是一樣,要結合媒體查詢與rem或px,因地制宜。

4)這個范圍,目前我還不能確定,還沒有做過詳細的實驗。

 

參考資料:

移動端前端開發中需要知道的一些屏幕知識
設計師DPI指南(翻譯)
理解flexible.js所需的viewport知識
使用Flexible實現手淘H5頁面的終端適配
dp、sp、px傻傻分不清楚
移動端H5頁面高清多屏適配方案
A tale of two viewports
關於移動端 rem 布局的一些總結
移動端頁面布局及字體大小該如何設置
web app變革之rem
蘋果的 Retina 屏幕一定需要突破 326 PPI 嗎?還是跟距離相關?
Retina顯示屏
(翻譯)第三種viewport-ideal viewport

關於meta viewport中target-densitydpi屬性


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM