1. 程式人生 > >移動 web 開發最佳實踐

移動 web 開發最佳實踐

0、問題的引出

提到移動裝置開發,最先讓人想起的是蘋果和安卓,以及他們那些令人頭疼的多螢幕適配。下面是騰訊分析統計的移動裝置的各解析度佔有情況。

不光解析度差別很大,移動裝置的尺寸相差也很大,從3英寸的手機到12英寸的平板。

下圖為各種品牌所存在的移動裝置的尺寸。

還有,就算兩個裝置尺寸一樣,也會存在不一樣的螢幕密度(dpi或ppi,每英寸的螢幕包含多少個畫素),同樣大小的字型或者寬高,放到這兩個裝置上,螢幕密度大的字型就會顯得小。下圖是現有市場上的螢幕檔次。

所以說,移動端web開發面臨的最大問題就是就是多屏適配,這是一個設計師、開發和測試都要面臨的問題,如何做到在不同解析度,不同螢幕密度上的手機上,同樣大小的UI元素,看起來是一樣大的。說白了就是同一套程式碼在不同解析度的手機上跑時,頁面元素間的間距,留白,以及圖片大小會隨著變化,在比例上跟設計稿一致。

1、一些概念

在解決問題之前,先了解一些概念。

CSS畫素

即我們css程式碼裡寫的畫素(px),用於頁面佈局的單位,與裝置無關。

物理畫素(px,physical pixel)

一個物理畫素是顯示器(手機螢幕)上最小的物理顯示單元,在作業系統的排程下,每一個裝置畫素都有自己的顏色值和亮度值。物理畫素與解析度有關,比如iphone 7的解析度是1334x750,它表示這個設配有1334x750個物理畫素。

裝置獨立畫素(dp,density-independent pixel)

裝置獨立畫素(也叫密度無關畫素),可以認為是計算機座標系統中得一個點,這個點代表一個可以由程式使用的虛擬畫素(比如: css畫素),然後由相關係統轉換為物理畫素。設配獨立畫素與尺寸有關,比如iphone7的寬高為375×667,可以理解為裝置有375×667獨立畫素(或css畫素)。所以說,物理畫素和裝置獨立畫素之間存在著一定的對應關係,這就是接下來要說的裝置畫素比。

裝置畫素比(device pixel ratio )

裝置畫素比(簡稱dpr)定義了物理畫素和裝置獨立畫素的對應關係,一個裝置畫素上有多少個物理畫素,它的值可以按如下的公式的得到:

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

下圖為同樣設定css寬高為2px的矩形,在不同的裝置上所佔的物理畫素。其中畫素比為1的佔用4個,畫素比為2的佔用16個。

視口(viewport):

視口指的是移動裝置中的裝置螢幕視窗。在移動端瀏覽器當中,存在著兩種視口,一種是視覺視口(也就是我們說的裝置大小),另一種是佈局視口(我們要看的網頁的寬度是多少)。

先說一下視口的起源,智慧裝置剛出現的時候,檢視桌面端的頁面時會出現一個問題:由於早期的頁面很多采用固定寬度的佈局,導致放在移動端的小視窗下出現橫向的滾動條,不便於使用者檢視,所以瀏覽器廠商研究出了佈局視口。佈局視口的寬度一般在768px~1024px(由瀏覽器廠商設定),常見寬度980px,這樣,小屏的移動裝置能夠一次性完全顯示桌面端頁面,避免了瀏覽器出現橫向滾動條。下圖是騰訊網(www.qq.com)在手機端的顯示狀態,如果不進行縮放操作的話,文字幾乎是無法看清的.

舉一個例子:如果我們的螢幕是375畫素×667畫素的大小(iPhone6),假設在瀏覽器中,375畫素的螢幕寬度能夠展示980畫素寬度的內容。那麼375畫素的寬度就是可見視口的寬度,而能夠顯示的980畫素的寬度就是視窗視口的寬度。說白了,就是把980px的東西裝在了375px的螢幕裡。使用者不用縮放,就能看到整屏的的頁面。但也產生了一個問題,移動端的瀏覽器同桌面端相比,就是字型過小,但是使用者可以手動縮放。後期也產生了根據調整視口寬度(width)和縮放(scale)開發移動端的頁面。

2、設計圖

設計師出圖的依據是移動裝置的解析度,與裝置的寬高無關,單位是px。根據本文的第一張圖顯示,蘋果1334750解析度最多,而安卓則是19201080最多,雖然有些差別,但是寬高比都是16:9的,縮放後失真不會太多。

1倍圖

在早期的手機,一個裝置畫素(dp)就是一個物理畫素px,這時候的解析度和尺寸有關。比如480x320的手機一定會比640x480的小。這種圖已成為歷史,不再討論。

2倍圖

後來蘋果發明了retina,就有了螢幕畫素比這個概念,2倍圖就是螢幕畫素比為2的圖,這種比例以iphone 4起為代表,iphone4它的尺寸是320x480,但是它的解析度是640x960。即:解析度 = 螢幕寬高 x 畫素比

3倍圖

到了iphone plus又出了三倍圖的概念,它的尺寸是是414x736 ,而解析度達到了1242x2208。

下面是iphone系列各個寬高及畫素比:

安卓的螢幕尺寸更加多樣,解析度有很多種,相應地,裝置畫素比也不一致,有1、1.5、2、2.25、3等等。也有1.5倍圖等概念。

那設計師出多大解析度(px)的圖呢?

第一種:

以iphone5(640x1136) 為基準設計稿,向上適配。這種方案在兩年前比較流行,小尺寸的頁面放在大尺寸的手機上,會自動等比放大,鋪滿新手機,效果還可行。現在還有很多H5在做適配的時候設定成都直接設定了 viewport:width=320,簡單可行。但因為拉伸,整體看起來有點虛,也不能更好利用大屏空間。等到後來在plus出現,效果就更差了一些。

第二種:

以iphone6 為基準設計稿,向上、向下適配。這個尺寸在H5上非常流行,iphone 6 6s 7的尺寸大小相同,解析度相同,都為750x1334,向上拉伸,向下壓縮,失真的比例不會太大。

第三種:

以iphone plus 為基準設計稿,向下適配。這兩年安卓發展突飛猛進,解析度越來越高,1080x1920解析度已成為普及,而2k、4k屏也即將到來,小的設計稿已無法滿足超清的要求,很多App的設計都已步入3倍圖的時代,那就是以iphone plus 的尺寸(414×3=1242)為基準,生成的3倍圖,1242×2208px解析度。

綜合來看,在移動web開發時,第二種方式當前最合適。既滿足了retina使用者的顯示需求,又能降低2G、3G使用者載入圖片需要的頻寬。不過,你若有更高質量的追求,第三種設計稿也是一個不錯的選擇。

不管在手機瀏覽器還是在微信客戶端或者騰訊新聞客戶端開發,內容大都不能全屏顯示的。在底部或者頂部多多少少會有一個狀態列的佔位。一些手機瀏覽器底部會有導航,也有些會在頂部和底部都有佔位,比如導航欄、狀態列。頂部的佔位會把內容往下擠,底部的佔位會把內容遮蓋住。如果做只有一屏的H5,高度要注意一下。

下圖為騰訊新聞客戶端和微信內建瀏覽器的佔位高度,在750x1334(iphone6)上他們的高度是一樣的。如果你的頁面高度超過1208px,頁面就會出現滾動功能。

3、適配

1、viewport固定

viewport <meta>用於指定使用者是否可以縮放Web頁面,表示文件針對移動裝置進行了優化。viewport <meta>的content值是由指令及其值組成的以逗號分隔的列表。

下面是viewport的一個示例:

<meta name="viewport" content="width=240, height=320, user-scalable=yes,
 initial-scale=2.5, maximum-scale=5.0, minimun-scale=1.0">

width和height的值可以使用具體的數值,或者使用device-width/device-height可以指示視區寬度應為裝置的螢幕寬度/高度。

user-scalable為yes/no,或者1/0表示是否允許使用者縮放。

initial-scale用於設定Web頁面的初始縮放比例,設為1.0則將顯示未經縮放的Web文件。>1將放大, <1將縮小。

maximum-scale和minimum-scale用於設定使用者對Web頁面縮放比例的限制。值的範圍為0.25至10.0之間。

既然viewport可以自動縮放頁面,那麼為什麼不製作固定尺寸的頁面,讓瀏覽器自己去縮放呢?開發和設計都省事了!

固定尺寸的頁面的實現:

<meta name="viewport" content="width=750,user-scalable=no">

這種模式最為簡單,它的意思是不管何種裝置,都按照750的寬度等比例縮放。所以,按照寬度為750的介面設計和開發最省事。user-scalable=no的意思是不允許使用者縮放。若是開啟使用者縮放的話,當你的頁面超過750px時,瀏覽器會自動進一步縮放,至你的頁面寬度。

另外,也有js的寫法

var phoneScale = parseInt(window.screen.width)/750;
var viewport = document.querySelector("meta[name=viewport]")
viewport.setAttribute('content',"width=750, initial-scale = '+phoneScale+',
 maximum-scale = '+phoneScale+', maximum-scale = '+phoneScale+'">');
}

這種模式適合用來做PC頁面的簡單適配,但是這有個缺點,這是縮放。既然是縮放,那麼就會失真,大屏裝置上的字型會大一些(字型變的模糊),1px的直線看起來不一樣粗。還有,裝置由豎屏切換到橫屏的時候,介面會變大的很多。在一些手機上,如果用了一些非自帶的字型,甚至會發虛,如果用了部分CSS3的屬性,發糊的現象可能會更嚴重,縮放同時會帶來瀏覽器的卡頓。

這種模式,情急之下夠用,但不完美。

2、裝置寬度

在開發移動網頁時,你一定會遇到前輩留下的這段程式碼:

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5,
 maximum-scale=2.0, user-scalable=yes" />

在網頁的中增加以上這句話,可以讓網頁的寬度自動適應手機螢幕的寬度。其中:

width=device-width :表示寬度是裝置螢幕的寬度initial-scale=1.0:表示初始的縮放比例

它的意思是說,頁面寬度就是裝置寬度,縮放比例100%,這時,無論你是多麼高清的屏一個CSS畫素完全等於一個裝置的獨立畫素。其實這兩行程式碼的作用是一樣的。他們的的作用都是不對當前的頁面進行縮放,也就是頁面本該是多大就是多大。因為這裡的縮放值是1,也就是沒縮放,螢幕的寬度自然是實際能展示的寬度了。

但如果width 和 initial-scale=1同時出現,並且還出現了衝突呢?比如:

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

width=400表示把當前viewport的寬度設為400px,initial-scale=1則表示把當前viewport的寬度設為裝置的的寬度,那麼瀏覽器到底該服從哪個命令呢?是書寫順序在後面的那個嗎?不是。當遇到這種情況時,瀏覽器會取它們兩個中較大的那個值。

但為什麼要寫兩個?這裡有個相容性的bug,當橫豎屏切換的時候,iphone、ipad的device-width會始終為豎屏的width,而winphone的initial-scale始終依據的是豎屏的width。最完美的寫法應該是,兩者都寫上去,這樣就 initial-scale=1 解決了 iphone、ipad的毛病,width=device-width則解決了IE的毛病。

如果不設定width或scale,瀏覽器則會預設width為980,然後計算scale = 頁面寬度/980。

3、媒體查詢

媒體查詢可以讓我們根據裝置顯示器的特性為其設定CSS樣式,配合rem,就可以讓寬屏的裝置顯示大號字型和寬的內容。rem(font size of the root element)是指相對於根元素(html)的字型大小的單位。是CSS3新增的一種單位,移動端基本都支援。

html{font-size:20px}
@media screen and (min-width:321px) and (max-width:375px){html{font-size:22px}}
@media screen and (min-width:376px) and (max-width:414px){html{font-size:24px}}
@media screen and (min-width:415px) and (max-width:639px){html{font-size:30px}}
@media screen and (min-width:640px) and (max-width:719px){html{font-size:40px}}
@media screen and (min-width:720px) and (max-width:749px){html{font-size:45px}}
@media screen and (min-width:750px) and (max-width:799px){html{font-size:47px}}
@media screen and (min-width:800px){html{font-size:50px}}

把與元素尺寸有關的css,如width,height,line-height,margin,padding等都以rem作為單位,這樣頁面在不同裝置下就能保持一致的網頁佈局。

假設,html我們設定font-size:20px; 也就是說20px相對於1rem,那麼18px也就是 18/20 = 0.9rem。

那麼我們以375px的設計佈局為基準,將html設定為font-size:100px,即100px = 1rem。(設定100px是為了方便計算)那麼可以將大部分px單位除以100就可以直接改成rem單位了。比如,70px寬的元素就是0.7rem。

通過裝置寬度範圍區間這樣的媒體查詢來動態改變rem基準值,其實不夠精確,比如:寬度為375px 和 寬度為321px的手機,因為屏寬在同一範圍區間內(320< width <375px),所以會被同等對待(rem基準值相同),而事實上他們的螢幕寬度並不相等,它們的佈局也應該有所不同。可以通過JS來實現媒體查詢的功能。

(function (doc, win) {
    var docEl = doc.documentElement,
        resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
        recalc = function () {
            var clientWidth = docEl.clientWidth;
            if (!clientWidth) return;
            docEl.style.fontSize = 100 * (clientWidth / 375) + 'px';
        };
    win.addEventListener(resizeEvt, recalc, false);
    doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);

到現在我們沒有做到足夠的精確,但是已經夠用。

4、1畫素問題

上述的方法還存在一個問題,先看下圖

我們設定了width=device-width,這樣css樣式和裝置畫素無關了,1px在普通屏上佔用了1行畫素,在高清屏上佔用了2行畫素,在3倍屏上就佔用了3行畫素等等。

那我們怎麼才能實現高清裝置上的實實在在的1px呢?也就是0.5px呢?比如設計師要求實現裝置上一條最細的邊線,可並不是所有手機瀏覽器都能識別border: 0.5px,ios7以下,android等其他系統裡,0.5px會被當成為0px處理的。

有兩個方法

第一種:針對邊框縮放

{
    border-bottom:1px solid #ddd;
    transform:scaleY(.5)
    transform-origin:0 0;
}

這個時候,不支援0.5px畫素的裝置會按1px實現,支援0.5px的裝置會呈現0.5px(實際上是1px)。這裡通過transform: scaleY(.5)縮小0.5倍來達到0.5px的效果,但是這樣hack實在是不夠通用(如:圓角等),與這個元素相關的其他屬性也要跟著調整,寫起來實在麻煩。

有沒有別的方法?有!

第二種:整體縮放

思路是這樣的,1倍圖渲染1倍圖的介面,2倍圖渲染高清屏的介面,以此類推,然後再統一縮小倍數到裝置介面上,完美!

var dpr, rem, scale;
var docEl = document.documentElement;
var fontEl = document.createElement('style');
var metaEl = document.querySelector('meta[name="viewport"]');

dpr = window.devicePixelRatio || 1;
rem = docEl.clientWidth * dpr / 10;
scale = 1 / dpr;

// 設定viewport,進行縮放,達到高清效果
metaEl.setAttribute('content', 'width=' + dpr * docEl.clientWidth 
+ ',initial-scale=' + scale + ',maximum-scale=' + scale 
+ ', minimum-scale=' + scale + ',user-scalable=no');

// 設定data-dpr屬性,留作的css hack之用
docEl.setAttribute('data-dpr', dpr);

// 動態寫入樣式
docEl.firstElementChild.appendChild(fontEl);
fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';

舉例來說:

1倍屏400寬的裝置rem是40px,渲染1rem的寬度實際上就是40px,渲染1px就是1px。

2倍屏400寬的裝置rem是80px,渲染1rem的寬度實際上就是是80px,縮放為0.5倍為40px,渲染1px就是1px。

2倍屏600寬的裝置rem是120px,渲染1rem的寬度實際上就是是120px,縮放為0.5倍為60px,渲染1px就是1px。

這樣就在,通過螢幕畫素比進行縮放,不改變原來rem大小的前提下,實現了1px功能。

5、一切從簡

上述的例子一步一步引出了動態設定rem的方法,但是每次都要計算rem很麻煩。

對於一些資訊流的頁面,比如騰訊新聞(xw.qq.com)iphone5的介面和iphone plus介面

對於圖片,採取了等比例縮放,對於文字,大小是一樣的,超出後自動截斷。每個列表的高度是一樣的,裝置越高,展示的新聞條數越多。

這是一種典型的彈性佈局:關鍵元素高寬和位置都不變,只有容器元素在做伸縮變換。對於這類頁面,記住一個開發原則就好:文字流式,控制元件彈性,圖片等比縮放

1、文字字型大小不變,多了就折行,自動撐開。

2、空間在寬屏的裝置上左右浮動,flex或者float。

3、圖片通過scale或百分比自動縮放。

對於這種頁面,拿到設計圖後,直接按px開發,無需用rem增加頁面的複雜度。

4、小結

上面對移動端H5高清和多屏適配的一些方案總結,沒有固定的套路及方法,請根據自身業務的特點,選擇其中的一些方法組合使用,不對的地方,多多指教。

接下來第二部分會分析移動web開發的過程中的細節問題和最優的解決方法。