移動開發不同與PC端開發,可能會經曆各種意想不到的問題,尤其是移動端應用剛起步的幾年;随着移動網際網路的快速發展,有些問題已經得到了很好的支援,如1像素邊界的問題。當然,要更好地解決這些移動端的問題,就需有移動端領域相關的知識,下面就來說說。
首先說一下,這個dpr不僅僅是移動端才有的,pc端也有,但是對一些移動端的問題産生的原因及解決顯得比較重要,比如1像素的問題。先來看幾個概念:
<code>實體像素(physical pixel)</code>
一個實體像素就是顯示裝置上最小的實體顯示單元,每個實體像素都有自己的顔色值和亮度值。例如iphone6手機螢幕有750*1334個實體像素
<code>裝置獨立像素(density-independent</code>
裝置獨立像素又叫密度無關像素,也可以叫邏輯像素,程式使用的虛拟像素如css像素,可以了解為顯示裝置坐标系統中的一個點;
<code>裝置像素比dpr(device pixel ratio)</code>
裝置像素比,簡稱dpr,定義了實體像素與裝置獨立像素之間的對應關系,具體的對應關系是一個計算公式如下:
上面計算的dpr是指某一個方向上如x或者y方向,二者dpr值相同;程式中擷取dpr方式如下:
js擷取dpr使用<code>window.devicePixelRatio</code>
css擷取dpr使用<code>-webkit-device-pixel-radio</code>
例如iphone6,裝置寬高375 * 667,可以了解為裝置獨立像素(也即css像素);其dpr為2,那麼對應的實體像素寬高均 * 2,即 750 * 1334;也就是說一個邏輯像素,在x軸和y軸都需要2個實體像素來顯示,一圖勝千言,如圖:

由上線描述可以知道,css中的1px并不等于顯示裝置的實體1px,這就導緻移動開發中設計師設計的是1px的實體像素,而轉換為css的值為1/dpr,其可能為小數值,這在低版本android(<=4.4)和ios(<=8)中被會系統自動轉換為0,這就是移動端常見的1px像素的問題;下面會給出具體的解決方案。
做過移動端開發的同學可能對下面html中的meta标簽比較熟悉:
這個是用來控制移動端<code>viewport</code>區域是怎麼展現的,很有必要對其了解。
在PC的浏覽器中,viewport其實就是浏覽器可視區域,但是在移動裝置上問題就比較複雜,viewport并不局限于浏覽器可視區域大小,可能比浏覽器可視區域要大,也可能比浏覽器可視區域要小;但是因為移動端螢幕相較pc端太窄,為在移動端正常顯示為PC端設計的網站,預設情況下移動端裝置上的viewport都是要大于浏覽器可視區域的,一般值為<code>980px</code>也可能有其他值,根據不同裝置來定;因為viewport比浏覽器可視區域大,那麼浏覽器就會出現橫向滾動條。
需要注意兩點:
頁面的html标簽的寬度就是相對于viewport的大小。
PC端viewport就是浏覽器可視區域大小;移動端預設viewport值為980px,也可以根據meta标簽自定義設定。
有關viewport理論,國外有一個人ppk對此有做過比較深入的研究,具體可以參考其寫的三篇文章①、②、③ 。其将viewport分成三個層面來了解:
layout viewport
布局視窗,網頁真正的布局視口,它的寬度可以大于也可以小于浏覽器可視區域的寬度,對于大于浏覽器可視區域(比如預設980px的viewport或者自定義設定viewport)的viewport,隻能通過滾動浏覽器滾動條來展現其内容。
visual viewport
可視視窗,移動裝置浏覽器可視區域的大小,其寬度并一定為移動顯示裝置的螢幕寬度,在initial-scale縮放為1的情況下才相等。
ideal viewport
理想化的視窗,它沒有一個固定的尺寸,其寬度為移動裝置螢幕寬度,它是最适合移動裝置的viewport。設定理想化的視窗的網頁不論何種分辨率的螢幕下,其使用者不需要縮放和橫向滾動條就能正常檢視網站所有内容,保證網頁的文字、圖檔等等其大小完美的呈現給使用者。
一圖勝千言,借用網上的幾張圖來說明具體的差別:
上面兩幅圖很好了解,下面兩幅對比圖,說明ideal viewport對于網站使用者體驗的重要性,使用者不用縮放或者滾動就能達到極佳的體驗效果。
用<code><meta name="viewport" content="xxx"></code> 标簽來控制viewport大小首先是由蘋果公司在其safari浏覽器中引入的,目的就是解決移動裝置的viewport問題。後來安卓以及各大浏覽器廠商也都紛紛效仿,引入它對viewport的支援。
viewport是通過<code>meta</code>标簽來控制頁面的viewport大小,具體形式如下:
其中<code>width=device-width</code>顧名思義是設定viewport的寬為裝置的寬,其還可以設定具體的邏輯像素值,如980。
meta标簽的viewport相關配置content值有6個屬性,它們可以同時使用,也可單獨使用,還可以混合使用,具體如下:
width
設定<code>layout viewport</code>的寬度,為一正整數,也可為device-width
height
設定<code>layout viewport</code>的寬度,為一正整數,很少使用
initial-scale
設定頁面的初始縮放值,為一數值,可帶小數
minimum-scale
設定頁面的最小縮放值,為一數值,可帶小數
maximum-scale
設定頁面的最大縮放值,為一數值,可帶小數
user-scalable
頁面是否允許縮放,值為"no"或"yes", no 不允許,yes允許
user-scalable=no禁止縮放在ios>=10系統的safari下有相容問題,具體可以看禁止頁面縮放meta标簽相容性問題這部分
meta屬性中initial-scale的縮放是相對于<code>ideal viewport</code>來進行的
這樣通過<code>meta</code>标簽很容易設定頁面layout viewport為移動裝置螢幕寬度(它也是ideal layout)即:
上面這種方式是最直接也是最輕易想到的設法,但是還可以使用initial-scale來達到同樣的效果,即:
簡單解釋下,<code>initial-scale</code>表示頁面初始縮放值,其縮放是相對于ideal viewport的來說的,為1表示不縮放,那麼其layout viewport的寬度就是ideal viewport的寬度,也就是移動裝置螢幕的寬度。
既然二者都可以設定layout viewport的寬度,那麼二者同時設定且值不相等會怎樣呢?答案是:
浏覽器會以二者中值較大的那個為準。
可能讀者會有新的疑惑,在設定layout viewport為螢幕寬度時,經常看到的是二者都寫上,為什麼呢?答案是:
一個是相容性的考慮,另一方面解決某些裝置橫豎屏不分導緻通通以ideal viewport的寬度為準的問題
上面提到,meta中的initial-scale是相對于ideal viewport進行縮放的,該屬性的作用:
initial-scale用來确定visual viewport即浏覽器可視區域寬度大小
阿裡早期的iphone/ipad下的自适應布局解決方案<code>flexible</code>就是利用initial-scale來解決的。
有人可能會有疑問,移動端浏覽器可視區域寬度不就是移動裝置螢幕的寬度麼?其實我們這裡所說的可視區域寬度是邏輯意義上的寬度,而非實際真實的寬度,例如iphone4的320px螢幕寬度,initial-scale放大2倍,那麼可視區域的邏輯大小變成了160px,可以通過檢視頁面html元素的寬度測試。
其實visual viewport與ideal viewport的關系如下:
在iphone/ipad下,在沒有指定initial-scale的情況下,無論你怎麼設定layout viewport寬度,它會根據上面的計算關系,自動計算目前頁面的inital-scale值以保證layout viewport寬度在縮放後就是浏覽器可視區域的的寬度,即inital-scale = ideal viewport寬度 / visual viewport寬度(等于layout viewport寬度)。
例如,iphone6情況下預設的layout viewport為980px,那麼目前縮放值inital-scale=375 / 980。
需要說明一下的是:在設定了initial-scale的情況下,這個自動計算的值就不起作用了。
移動端裝置不同,其螢幕大小也不盡相同,那麼針對特定移動裝置的頁面設計ui怎麼在不同移動裝置上因裝置不同而自适應螢幕展示呢。一般常見的解決方案有rem和vw/vh。下面就來說說。
<code>rem</code>是一個相對機關,相對于<code><html></code>的<code>font-size</code>來說的;那麼參與頁面布局的機關使用<code>rem</code>而不是px,這樣我們隻需控制不同裝置下網頁<code><html></code>的<code>font-size</code>即可做到頁面的自适應。
可以像flexible一樣将移動螢幕寬度100等份,每份為a,同時1rem認定為10a;例如750px的設計稿來說,這樣的話:
實作代碼如下:
另外,我們也可以基于特定的設計稿尺寸來計算其他移動裝置下的font-size,例如基于750px的設計稿,我們假設其font-size為40px,那麼其他裝置下font-size的計算關系式:
\[750/40 = 裝置螢幕寬/fontsize
\]
對應的核心轉換代碼即: <code>docEl.style.fontSize = (width / 750)*40 + 'px'</code>
首先要知道vw和vh的概念代表什麼,它也是相對機關,相對于螢幕的寬高而言的。
跟rem類似,我們可以使用vw機關作為css的唯一機關,這樣所有元素基于vw來布局;基于特定設計稿的尺寸來轉換vw機關,我們使用stylus預編譯函數來進行轉換:
然後無論是文本還是布局高寬、間距等都使用 vw 作為 CSS 機關,如
單純的vw适配在視口縮放時尤其是縮小時有些小瑕疵,因為vw是利用視口機關實作的布局,依賴視口的大小而自動縮放,也就失去了最大最小的寬度限制。一種比較好的解決方法是使用vw與rem配合來進行适配,即:
頁面需要适配的元素使用rem為機關,而的font-size值是根據vw來設定的,但是該font-size值需要限制最大最小值。
具體的stylus代碼如下,也可以參考這個demo猛戳
通過類似如下形式來實作适配:
該方式比較簡單,成本低,但是代碼量大,比較臃腫,維護不友善,不推薦該方式。
該方案随着<code>viewport機關</code> 得到衆多浏覽器的相容支援已逐漸不推薦使用了,它主要是為了解決iphone系列适配問題;雖然官方已不推薦使用,但是其思想還是值得借鑒學習的,主要表現下面三個方面:
根據<code>dpr</code>的值來修改<code><meta></code>的<code>viewport</code>的值實作移動端<code>1px</code>的問題
根據<code>dpr</code>的值來修改<code><html></code>的<code>font-size</code>值,使用以<code>rem</code>為機關值來等比例縮放
使用hack手段用<code>rem</code>模拟<code>vw</code>特性
對應第一點,它通過hack手段來根據裝置的<code>dpr</code>值相應改變<code><meta></code>标簽中的<code>viewport</code>的值:
這樣,iphone系統的不同裝置下頁面達到的效果是使css 1px像素與實體1px像素相同;然後,flexible使用rem作為布局機關實作适應布局。關鍵基本代碼如下:
産生1px邊框的問題其實歸結三點:
1px的css邏輯像素不等于1px的實體像素
ios<8以及Android<=4.4以下的浏覽器處理0.5px時會轉化為0
對于1px邊框問題的解決,<code>flexible</code>能完美的解決,思路見上面分析的;除此之外還有什麼方式,其實網上有很多方法,但是還是列一下思路:
用border-image來實作
有關border-image的請猛戳border-image 的正确用法;需要使用一張圖檔來充當border,圖檔大小是6px X 6px,如下:
缺點:改邊框顔色時要修改圖檔,不靈活
用背景漸變來實作
設定1px的漸變背景,50%有顔色,50%透明
缺點:維護過多代碼,圓角沒法實作
用box-shadow模拟邊框來實作
缺點:顔色不好處理,有陰影出現
用僞類+transform來實作
比較推薦的方法,原理是把原先元素的border去掉,然後利用<code>:before</code>或者<code>:after</code>重做border,并把<code>transform</code>的<code>scale</code>縮小一半,原先元素相對定位,僞元素模拟的border采用絕對定位。
Configuring the Viewport
H5移動多終端适配全解 - 從原理到方案
移動前端開發之viewport的深入了解
再聊移動端頁面的适配
利用@media screen實作網頁布局的自适應
利用視口機關實作适配布局
再談Retina下1px的解決方案
Web 端螢幕适配方案
使用Flexible實作手淘H5頁面的終端适配