1. 程式人生 > >你必須要知道的移動端開發知識

你必須要知道的移動端開發知識

移動開發不同與PC端開發,可能會經歷各種意想不到的問題,尤其是移動端應用剛起步的幾年;隨著移動網際網路的快速發展,有些問題已經得到了很好的支援,如1畫素邊界的問題。當然,要更好地解決這些移動端的問題,就需有移動端領域相關的知識,下面就來說說。

dpr裝置畫素比

首先說一下,這個dpr不僅僅是移動端才有的,pc端也有,但是對一些移動端的問題產生的原因及解決顯得比較重要,比如1畫素的問題。先來看幾個概念:

  1. 物理畫素(physical pixel)

    一個物理畫素就是顯示裝置上最小的物理顯示單元,每個物理畫素都有自己的顏色值和亮度值。例如iphone6手機螢幕有750*1334個物理畫素

  2. 裝置獨立畫素(density-independent

    裝置獨立畫素又叫密度無關畫素,也可以叫邏輯畫素,程式使用的虛擬畫素如css畫素,可以理解為顯示裝置座標系統中的一個點;

  3. 裝置畫素比dpr(device pixel ratio)

    裝置畫素比,簡稱dpr,定義了物理畫素與裝置獨立畫素之間的對應關係,具體的對應關係是一個計算公式如下:

    dpr = 物理畫素 / 裝置獨立畫素

    上面計算的dpr是指某一個方向上如x或者y方向,二者dpr值相同;程式中獲取dpr方式如下:

    • js獲取dpr使用window.devicePixelRatio

    • css獲取dpr使用-webkit-device-pixel-radio

    例如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畫素的問題;下面會給出具體的解決方案。

viewport

做過移動端開發的同學可能對下面html中的meta標籤比較熟悉:

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

這個是用來控制移動端viewport區域是怎麼展現的,很有必要對其理解。

viewport理解

在PC的瀏覽器中,viewport其實就是瀏覽器可視區域,但是在移動裝置上問題就比較複雜,viewport並不侷限於瀏覽器可視區域大小,可能比瀏覽器可視區域要大,也可能比瀏覽器可視區域要小;但是因為移動端螢幕相較pc端太窄,為在移動端正常顯示為PC端設計的網站,預設情況下移動端裝置上的viewport都是要大於瀏覽器可視區域的,一般值為980px也可能有其他值,根據不同裝置來定;因為viewport比瀏覽器可視區域大,那麼瀏覽器就會出現橫向滾動條。

需要注意兩點:

  1. 頁面的html標籤的寬度就是相對於viewport的大小。

  2. PC端viewport就是瀏覽器可視區域大小;移動端預設viewport值為980px,也可以根據meta標籤自定義設定。

有關viewport理論,國外有一個人ppk對此有做過比較深入的研究,具體可以參考其寫的三篇文章①、②、③ 。其將viewport分成三個層面來理解:

  • layout viewport

    佈局視窗,網頁真正的佈局視口,它的寬度可以大於也可以小於瀏覽器可視區域的寬度,對於大於瀏覽器可視區域(比如預設980px的viewport或者自定義設定viewport)的viewport,只能通過滾動瀏覽器滾動條來展現其內容。

  • visual viewport

    可視視窗,移動裝置瀏覽器可視區域的大小,其寬度並一定為移動顯示裝置的螢幕寬度,在initial-scale縮放為1的情況下才相等。

  • ideal viewport

    理想化的視窗,它沒有一個固定的尺寸,其寬度為移動裝置螢幕寬度,它是最適合移動裝置的viewport。設定理想化的視窗的網頁不論何種解析度的螢幕下,其使用者不需要縮放和橫向滾動條就能正常檢視網站所有內容,保證網頁的文字、圖片等等其大小完美的呈現給使用者。

一圖勝千言,借用網上的幾張圖來說明具體的區別:

上面兩幅圖很好理解,下面兩幅對比圖,說明ideal viewport對於網站使用者體驗的重要性,使用者不用縮放或者滾動就能達到極佳的體驗效果。

用meta標籤控制viewport

<meta name="viewport" content="xxx"> 標籤來控制viewport大小首先是由蘋果公司在其safari瀏覽器中引入的,目的就是解決移動裝置的viewport問題。後來安卓以及各大瀏覽器廠商也都紛紛效仿,引入它對viewport的支援。

viewport是通過meta標籤來控制頁面的viewport大小,具體形式如下:

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

其中width=device-width顧名思義是設定viewport的寬為裝置的寬,其還可以設定具體的邏輯畫素值,如980。

meta標籤的viewport相關配置content值有6個屬性,它們可以同時使用,也可單獨使用,還可以混合使用,具體如下:

width 設定layout viewport的寬度,為一正整數,也可為device-width
height 設定layout viewport的寬度,為一正整數,很少使用
initial-scale 設定頁面的初始縮放值,為一數值,可帶小數
minimum-scale 設定頁面的最小縮放值,為一數值,可帶小數
maximum-scale 設定頁面的最大縮放值,為一數值,可帶小數
user-scalable 頁面是否允許縮放,值為"no"或"yes", no 不允許,yes允許

需要注意兩點:

  1. user-scalable=no禁止縮放在ios>=10系統的safari下有相容問題,具體可以看禁止頁面縮放meta標籤相容性問題這部分

  2. meta屬性中initial-scale的縮放是相對於ideal viewport來進行的

這樣通過meta標籤很容易設定頁面layout viewport為移動裝置螢幕寬度(它也是ideal layout)即:

<meta name="viewport" content="width=device-width">

上面這種方式是最直接也是最輕易想到的設法,但是還可以使用initial-scale來達到同樣的效果,即:

<meta name="viewport" content="initial-scale=1">

簡單解釋下,initial-scale表示頁面初始縮放值,其縮放是相對於ideal viewport的來說的,為1表示不縮放,那麼其layout viewport的寬度就是ideal viewport的寬度,也就是移動裝置螢幕的寬度。

既然二者都可以設定layout viewport的寬度,那麼二者同時設定且值不相等會怎樣呢?答案是:

瀏覽器會以二者中值較大的那個為準。

可能讀者會有新的疑惑,在設定layout viewport為螢幕寬度時,經常看到的是二者都寫上,為什麼呢?答案是:

一個是相容性的考慮,另一方面解決某些裝置橫豎屏不分導致通通以ideal viewport的寬度為準的問題

viewport的縮放

上面提到,meta中的initial-scale是相對於ideal viewport進行縮放的,該屬性的作用:

initial-scale用來確定visual viewport即瀏覽器可視區域寬度大小

阿里早期的iphone/ipad下的自適應佈局解決方案flexible就是利用initial-scale來解決的。

有人可能會有疑問,移動端瀏覽器可視區域寬度不就是移動裝置螢幕的寬度麼?其實我們這裡所說的可視區域寬度是邏輯意義上的寬度,而非實際真實的寬度,例如iphone4的320px螢幕寬度,initial-scale放大2倍,那麼可視區域的邏輯大小變成了160px,可以通過檢視頁面html元素的寬度測試。

其實visual viewport與ideal viewport的關係如下:

visual viewport = ideal viewport / initial-scale

在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。下面就來說說。

rem適配

rem是一個相對單位,相對於<html>font-size來說的;那麼參與頁面佈局的單位使用rem而不是px,這樣我們只需控制不同裝置下網頁<html>font-size即可做到頁面的自適應。

可以像flexible一樣將移動螢幕寬度100等份,每份為a,同時1rem認定為10a;例如750px的設計稿來說,這樣的話:

1a = 750px / 100 = 7.5px
1rem = 10a = 75px

實現程式碼如下:

(function (doc, win) {
var docEl =doc.documentElement,
    resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
    recalc = function () {
      var width = docEl.getBoundingClientRect().width;
      if (!width) return;
      docEl.style.fontSize = (width / 100)*10 + 'px';
    };

  if (!doc.addEventListener) return;
  win.addEventListener(resizeEvt, recalc, false);
  recalc();
  // hack相容某些特殊機型
  doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);

另外,我們也可以基於特定的設計稿尺寸來計算其他移動裝置下的font-size,例如基於750px的設計稿,我們假設其font-size為40px,那麼其他裝置下font-size的計算關係式:

\[ 750/40 = 裝置螢幕寬/fontsize \]

對應的核心轉換程式碼即: docEl.style.fontSize = (width / 750)*40 + 'px'

VW適配

首先要知道vw和vh的概念代表什麼,它也是相對單位,相對於螢幕的寬高而言的。

100vw = ideal viewport 寬

100vh = ideal viewport 高

跟rem類似,我們可以使用vw單位作為css的唯一單位,這樣所有元素基於vw來佈局;基於特定設計稿的尺寸來轉換vw單位,我們使用stylus預編譯函式來進行轉換:

$vw_base = 375
vw(px) {
   (px/$vw_base) * 100vw
}

然後無論是文字還是佈局高寬、間距等都使用 vw 作為 CSS 單位,如

.container
  padding: vw(15) vw(10) vw(10)
  height: vw(40)

vw+rem結合適配

單純的vw適配在視口縮放時尤其是縮小時有些小瑕疵,因為vw是利用視口單位實現的佈局,依賴視口的大小而自動縮放,也就失去了最大最小的寬度限制。一種比較好的解決方法是使用vw與rem配合來進行適配,即:

頁面需要適配的元素使用rem為單位,而的font-size值是根據vw來設定的,但是該font-size值需要限制最大最小值。

具體的stylus程式碼如下,也可以參考這個demo猛戳

$fontsize = 75 // 將螢幕分成10份
$base = 750 // iphone6 750作為基數
rem(px) {
    (px/$fontsize) * 1rem
}

html 
  font-size: ($fontsize / $base) * 100vw //font-size以vw為單位
  // 通過medai query限制根元素的最大值最小值
  @media screen and (max-width: 320px) {// 頁面寬度<=320時生效
      font-size: 64px
  }
  @medai screen and (min-width: 540px) { // 頁面寬度>=540時生效
      font-size: 108px
  }

媒體查詢media query適配

通過類似如下形式來實現適配:

/* 大於1200px */
@media screen and (min-width:1200px){}
/* 大於等於960px,小於1200px */
@media screen and (min-width: 960px) and (max-width: 1199px){}
/* 大於等於768px,小於960px */
@media screen and (min-width: 768px) and (max-width: 959px){}
/* 大於等於480px,小於768px */
@media only screen and (min-width: 480px) and (max-width: 767px){}
/* 小於479px */
@media only screen and (max-width: 479px){}

該方式比較簡單,成本低,但是程式碼量大,比較臃腫,維護不方便,不推薦該方式。

阿里flexible適配

該方案隨著viewport單位 得到眾多瀏覽器的相容支援已逐漸不推薦使用了,它主要是為了解決iphone系列適配問題;雖然官方已不推薦使用,但是其思想還是值得借鑑學習的,主要表現下面三個方面:

  • 根據dpr的值來修改<meta>viewport的值實現移動端1px的問題

  • 根據dpr的值來修改<html>font-size值,使用以rem為單位值來等比例縮放

  • 使用hack手段用rem模擬vw特性

對應第一點,它通過hack手段來根據裝置的dpr值相應改變<meta>標籤中的viewport的值:

<!-- dpr = 1-->
<meta name="viewport" content="initial-scale=scale,maximum-scale=scale,minimum-scale=scale,user-scalable=no">
 <!-- dpr = 2-->
<meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">
 <!-- dpr = 3-->
<meta name="viewport" content="initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no">

這樣,iphone系統的不同裝置下頁面達到的效果是使css 1px畫素與物理1px畫素相同;然後,flexible使用rem作為佈局單位實現適應佈局。關鍵基本程式碼如下:

// 設定meta的viewport內容進行縮放
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
    // iOS下,對於2和3的屏,用2倍的方案,其餘的用1倍方案
    if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
        dpr = 3;
    } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
        dpr = 2;
    } else {
        dpr = 1;
    }
} else {
    // 其他裝置下,仍舊使用1倍的方案
    dpr = 1;
}
scale = 1 / dpr;

// 確定html的font-size值
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
    width = 540 * dpr;
}
var rem = width / 10; // 螢幕均分100份,每份為a,1rem為10a
docEl.style.fontSize = rem + 'px';

1px邊框問題及解決方案

產生1px邊框的問題其實歸結三點:

  • 1px的css邏輯畫素不等於1px的物理畫素

  • ios<8以及Android<=4.4以下的瀏覽器處理0.5px時會轉化為0

對於1px邊框問題的解決,flexible能完美的解決,思路見上面分析的;除此之外還有什麼方式,其實網上有很多方法,但是還是列一下思路:

  1. 用border-image來實現

    使用一張圖片來充當border,圖片形式是3x3,如下:

    .border {
        border-width: 1px;
        border-image: url(border.png) 2 repeat;
    }

    缺點:改邊框顏色時要修改圖片,不靈活

  2. 用背景漸變來實現

    設定1px的漸變背景,50%有顏色,50%透明

    .border {
        background-image: linear-gradient(180deg, green, green 50%, transparent 0);
      background-size: 100% 1px; /* 背景寬度100%,高度1px */
      background-repeat: no-repeat;
      background-position: bottom;
    }

    缺點:維護過多程式碼,圓角沒法實現

  3. 用box-shadow模擬邊框來實現

    .border {
        border: none;
        height: 100px;
        width: 100%;
        box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
    }

    缺點:顏色不好處理,有陰影出現

  4. 用偽類+transform來實現

    比較推薦的方法,原理是把原先元素的border去掉,然後利用:before或者:after重做border,並把transformscale縮小一半,原先元素相對定位,偽元素模擬的border採用絕對定位。

    .border {
        position: relative;
        border: none;
    }
    .border:after {
        content: ' ';
        position: absolute;
        left: 0;
        background: #666;
        width: 100%;
        height: 1px;
        tranform: scaleY(0.5);
        transform-origin: 0 0;
    }

參考文獻

  • Configuring the Viewport

  • H5移動多終端適配全解 - 從原理到方案

  • 移動前端開發之viewport的深入理解

  • 再聊移動端頁面的適配

  • 利用@media screen實現網頁佈局的自適應

  • 利用視口單位實現適配佈局

  • 再談Retina下1px的解決方案

  • Web 端螢幕適配方案

  • 使用Flexible實現手淘H5頁面的終端適配