移動端適配-實踐篇

Momomo發表於2020-01-04

這是一個系列文章,分 3 篇:

一般我們在做前端專案之前,都會先拿到視覺稿和互動稿,我們可以根據視覺稿上的尺寸、顏色等資訊編寫 CSS 樣式,可以根據互動稿來寫 JS。

每個公司的視覺規範不同,視覺稿也就不同。甚至在一些大公司裡,每個部門都有自己的視覺規範。比如頁面畫布大小是以 640 為基準還是 750。最終給前端開發人員的檔案也可能不同,比如 PSD 檔案、Sketch 檔案或者圖片檔案,其中包括頁面檔案和切圖。

我做過的專案裡,有這幾種情況:

  • Sketch檔案包 + 總體設計圖 + 每個頁面圖片 + 切圖(@2x、@3x 各一份)
  • PSD 原始檔 + 總體設計圖 + 每個頁面圖片(有的帶尺寸/顏色標註) + 切圖(@2x、@3x 各一份)
  • 只給帶標註的圖片的,這種我都會問UED要原始檔,標註很難畫全的。

我們 UED 畫布基準都是 iphone6,但是有的是給的畫布寬 375 的 Sketch 檔案,有的給畫布寬 750 的 PSD 檔案。

下面以我做過的一個頁面為例,左邊是Sketch 檔案的截圖,右邊是 PSD 檔案匯出的圖片。

移動端適配-實踐篇

這個頁面涉及到了移動端適配的幾個問題:

  • 佈局適配,不同螢幕尺寸的裝置中佈局一致
  • 圖片高清適配,不同解析度裝置中圖片高清展示
  • 不同解析度裝置中 1px 邊框顯示一致
  • 內容適配,不同螢幕尺寸的裝置中的文字大小

那,拿到視覺稿了,我們要開始寫程式碼了。

假如我們拿到的是寬 750 的視覺稿
**
在寫 CSS 程式碼前還得做件事兒,手機端佈局視口預設情況下是 768px ~ 1024px 之間,手機螢幕寬度大家知道的iphone5 320px, iphone6 375px, iphone6 plus 414px 這樣子。

像下面圖示這樣,在手機上瀏覽頁面時得橫向滾動,是不是很不友好?

移動端適配-實踐篇

所以我們得想辦法,讓頁面橫向內容都展示在螢幕可視範圍中,並且禁止縮放。有兩種辦法:
  • 設定佈局視口寬度為一個定值,然後按照佈局視口的寬度與螢幕寬度的比例縮放
  • 設定佈局視口的寬度 = 裝置寬度

設定佈局視口寬度為一個定值,按照佈局視口的寬度與螢幕寬度的比例縮放

比如螢幕寬 375px,視覺稿 750px,那就設定佈局視口的寬度 width=750,
scale = 375 / 750 = 0.5

<meta name="viewport" content="width=750,initial-scale=0.5,maximum-scale=0.5,user-scalable=no">
複製程式碼

再比如螢幕寬 320px,視覺稿 750px,那就設定佈局視口的寬度 width=750,
scale = 320 / 750

當然這是需要根據螢幕寬度動態設定的:

(function(){
	var doc = window.document;
	var metaEl = doc.querySelector('meta[name="viewport"]');
	if(!metaEl){
		metaEl = doc.createElement("meta");
		metaEl.setAttribute("name", "viewport");
	}
	var metaCtt = metaEl ? metaEl.content : '';
	var matchWidth = metaCtt.match(/width=([^,\s]+)/);
	var width = matchWidth ? matchWidth[1] : 750
	if(width == 'device-width'){return}
	
	var screenWidth = window.screen.width;
	var scale = screenWidth/width;
	metaEl.setAttribute("content", "width="+ width +",user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
})()
複製程式碼

demo1
下面是依次在 iphone6 plus,iphone6,iphone5 上的展示效果。

image.png

那有人就會問啦,給的視覺稿是基於 iphone5 的,畫布寬度 640 怎麼辦?
**
那 CSS 程式設計就按照視覺給的來,width 設定 640,在寬 640 的佈局視口,CSS 樣式編寫與視覺稿相同,那佈局就能與視覺稿相同啦。再通過動態縮放就能把整個頁面完整的展示在可視視窗中啦。

那又有人問啦,給的不是 750 寬的視覺稿,是 Sketch 視覺稿,寬 375,這怎麼辦?
**
一樣的道理,視覺稿寬 375,視覺稿中的元素都是按寬 375 來佈局的,那我們佈局視口 width 設定 375,CSS 樣式按375的視覺稿來寫就好啦,最後就是動態縮放啦。

好啦,那我們來看這種方式能不能解決我們遇到的幾個問題。

圖片高清問題

**
實際要解決的是怎麼讓 1 個點陣圖畫素正好覆蓋 1 個物理畫素,這樣可以讓圖片在不同 dpr 的手機上效果一樣。

  • dpr = 1,需要 1 倍圖
  • dpr = 2,需要 2 倍圖
  • dpr = 3,需要 3 倍圖

最好的解決辦法是:不同的 dpr 下,載入不同的尺寸的圖片

1px 邊框問題

**
實際要解決的是怎麼讓 1 個 CSS 畫素正好覆蓋 1 個物理畫素,這樣可以讓 1px 邊框在不同 dpr 的手機上一樣細。
但是佈局視口縮放比為 1/dpr 才能讓 1 個 CSS 畫素是否正好覆蓋1個物理畫素。

顯然這種按照佈局視口的寬度與螢幕寬度的比例縮放的方式,只有與視覺稿畫布基準一致的裝置才能與視覺稿要求顯示的一致。比如這裡只有在 iphone6 上 1 個 CSS 畫素是否正好覆蓋1個物理畫素(橫向 200px 的元素對應 400 個物理畫素,在縮放 0.5 後,橫向 200px 的元素對應 200 個物理畫素,1:1)

佈局適配的問題

其實直接從上面的對比圖就可以看出來,在不同裝置上佈局是一致的。
原理很簡單: 我們寫 CSS 本來就是按視覺稿來寫的,然後整體縮放,以適應各種寬度的裝置,元素的比例沒有變。

內容適配的問題

主要是文字大小,因為是整體縮放,螢幕越大,字型也越大。
這個主要看視覺規範怎麼定,有的覺得這樣挺好,有的就希望字型大小都一樣,還有的希望字型大小不是根據螢幕大小按比例縮放而是對一定範圍內的螢幕寬度設定特定的字型大小。

總結

【原理】

設定佈局視口寬度為視覺稿畫布寬度,動態設定縮放比例scale=螢幕寬度/視覺稿畫布寬度

【優點】

實現簡單,可解決不同螢幕大小的佈局問題,在各種螢幕上佈局一致

【缺點】

  • 不能解決 1px 邊框問題;
  • 縮放值依賴於螢幕寬度,demo 裡是通過 screen.width 獲取螢幕寬度的,這在 chrome 裡 iphone6 返回的是螢幕寬度值 375,但在其他瀏覽器就不一定了,比如safari中返回的是 1280。
  • 所有元素都會縮放,比如字型,在 iphone5 上就會小很多,這不一定是大家想要的。

設定佈局視口的寬度 = 裝置寬度

<meta 
      name="viewport" 
      content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
複製程式碼

這種情況下有什麼效果呢?

demo2

圖片太長,橫著放了^^

image.png

看看看,都被擠掉了,為啥?
這個是 iphone6 的截圖,現在設定了佈局視口寬度是螢幕寬度 375,我們視覺稿是按 iphone6 二倍圖來的寬 750,那當然就放不下了,比如視覺稿中圖片佔位 400px x 400px,我們 CSS 中寫的:

img{
	width: 400px;
	height: 400px;
}
複製程式碼

但是佈局視口寬度就 375,圖片就放不下了。
那 iphone6 佈局視口寬只有 375,我們把視覺稿尺寸全部按比例縮小到 375 就好啦。那 750 到 375,需要除以 2,我們在編寫 CSS 樣式時除以 2 就好啦。

上面視覺稿中圖片是 400 x 400,我們 CSS 中就寫:

img{
	width: 200px;
	height: 200px;
}
複製程式碼

其他元素的寬高、邊框、內邊距、外邊距、字型大小等,通通先除以 2,通通先除以 2。那現在的效果是這樣的,依次是 iphone6 plus,iphone6, iphone5

image.png

現在,我們能看到 100% 呈現在可視視窗中了。那再來看看我們遇到的問題解決了嗎?

**1px 邊框問題

設定 0.5px,在 dpr=2 的裝置中對應 1 個物理畫素,在 dpr=3 的裝置中對應 1.5 個物理畫素。
而且在 dpr=2 的裝置中設定 0.5px 不一定能達到我們想要的效果,看下面:

image.png

明明設定的 0.5px,但瀏覽器實際還是按 1px 處理的。ios7 以下,android 等其他系統裡,0.5px 會被當成為 0px 處理? 所以直接設定成 0.5px 是不可行的。 這實際上還是需要 1 個 CSS 畫素正好覆蓋 1  個物理畫素就可以解決的。

**佈局適配的問題

因為沒有按裝置屬性動態縮放,每個裝置上每個css畫素大小都一樣,200px 大小的元素在每個裝置上也夠一樣長。
其實從上面的對比圖就能看出來,圖片容器(灰色背景)在不同螢幕大小的手機都是一樣的高,整個頁面在小屏手機中就需要向下滾動檢視,一排展示的 4 個圖片標籤在大屏手機中右邊的空白就比較大。佈局問題也沒有解決。

**內容適配的問題 **
文字大小統統一樣,也沒有解決。

1 個 css 畫素只佔 1 個物理畫素

前面已經講了幾遍了,要解決 1px 邊框的問題,達到在不同解析度裝置上效果一致(當然也與視覺稿一致),需要讓不同解析度裝置上  1 個 css 畫素只覆蓋 1 個物理畫素

前面我們將 750 視覺稿中的尺寸除以 2 來編寫樣式,只能在 iphone6 上的效果滿足。那現在我們在佈局寬度=螢幕寬度時,讓縮放比例為 1/dpr,就可以讓 1 個 css畫素只覆蓋1個物理畫素

image.png

比如 750 的視覺稿,元素邊框 1px,我們 CSS 樣式也寫:

div{
	border-width: 1px;
}
複製程式碼

然後 JS 動態設定縮放邏輯大概是這樣子:

var doc = document;
var docEl = document.documentElement;
var isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
var dpr = window.devicePixelRatio || 1;
if (!isIos){ dpr = 1 }
var scale = 1 / dpr;
var metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
    metaEl = doc.createElement("meta");
    metaEl.setAttribute("name", "viewport");
    doc.head.appendChild(metaEl)
}
metaEl.setAttribute("content", "width=device-width,user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
複製程式碼

那現在無論什麼解析度的裝置,1個css畫素實際只佔1個物理畫素了。
那現在效果是什麼樣子呢?

demo3

image.png

所有元素尺寸都縮小 1/dpr 了,本質上是 1 個 CSS 畫素寬度縮小了 1/dpr,不同 dpr 的裝置上 CSS 畫素大小就不同了。那我們 CSS 樣式 200px,在不同 dpr 的裝置上所佔寬度也不同了。

注意: 如果在安卓裝置上檢視 demo3,與 demo2 的效果相同,因為 dpr 都被處理成 dpr=1,沒有縮放,CSS 樣式又與 750 視覺稿一致。所以這種方式只縮放,不控制佈局是不行的。
**
這個問題可以在待會講佈局適配時解決,佈局適配的目的是讓元素在不同裝置上佔比一致(與視覺稿相同。)

解決佈局適配問題

可以看下demo3的圖

  • 不同螢幕大小的手機都是一樣的高,在小屏手機中就需要向下滾動檢視。
  • 選擇圖片標籤下一行展示4個,但在大屏手機中右邊的空白就比較大。

那我們希望,元素在佈局視口中的佔比(包括元素寬高、邊距等)在不同裝置上都相同
同一份樣式怎麼讓元素佔比相同呢?用絕對單位 px 肯定是不行的,我們考慮相對單位 rem。

假如有個寬可以表示為 20rem 的元素:

CSS 樣式 CSS 畫素個數
width:20rem 20 * html元素的font-size
元素佔比 = 元素 CSS 畫素個數 / 佈局視口寬度
        = 20 * html元素的font-size / 佈局視口寬度
複製程式碼

所以只要滿足:

常量值 *  HTML 元素的 font-size =  佈局視口寬度
複製程式碼

元素佔比在任何裝置上都是某一個定值了。
那我們只要按下面的公式動態設定 HTML 元素的 font-size 就好啦。

HTML 元素的 font-size = 佈局視口寬度 / 常量值
複製程式碼

這個常量值可以是任意一個常數,但為了寫樣式方便,我們會做一些考慮,例如:

為了解決前面 1px 邊框 retina 屏顯示的問題,頁面縮放了 1/dpr,iphone6 佈局視口的寬度就是:375 * 2 個 css 畫素
我們可以讓 iphone6 中 HTML 元素的 font-size = 375 * 2 / 7.5 = 100,這樣可以直接將視覺稿中的尺寸除以100,得到 rem 單位的數值

對於以 iphone6 為基準的 750 視覺稿,並且縮放 1/dpr,HTML 元素的 font-size 與 佈局視口寬度視口的比例可以是:

HTML 元素的 font-size = 佈局視口寬度 / 7.5
複製程式碼

對於以 iphone5 為基準的 640 視覺稿,並且縮放 1/dpr

HTML 元素的 font-size = 佈局視口寬度 / 6.4
複製程式碼

大概的實現就是這樣子:

var doc = document;
var docEl = document.documentElement;
var isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
var dpr = window.devicePixelRatio || 1;
if (!isIos){ dpr = 1 }
var scale = 1 / dpr;
var metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
    metaEl = doc.createElement("meta");
    metaEl.setAttribute("name", "viewport");
    doc.head.appendChild(metaEl)
}
metaEl.setAttribute("content", "width=device-width,user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
setTimeout(function(){
	var width = docEl.getBoundingClientRect().width;
	docEl.style.fontSize = width / 7.5 + 'px';
})
複製程式碼

因為 scale=1/dpr && 基於 750 寬的視覺稿,元素的CSS 樣式(rem)可以直接由視覺稿尺寸除以 100
最後的效果是:

demo4

HTML 元素的 font-size = 佈局視口寬度 / 10
複製程式碼

demo5

HTML 元素的 font-size = 佈局視口寬度 / 7.5
複製程式碼

image.png

那有人會問啦,我的視覺稿是基於 iphone6 的,但給的是一倍圖寬 375 怎麼辦?
**
視覺稿一般有兩種型別: 一倍圖 和 二倍圖,比如基於 iphone6 的:

畫布寬 750,元素 400 x 400
畫布寬 375,元素 200 x 200

縮放一般有兩種:

scale = 1,佈局視口寬度 = 375 個 CSS 畫素
scale = 1/dpr,佈局視口寬度 = 750 個 CSS 畫素

如果視覺稿選擇畫布寬 750,並且 scale=1,那元素 CSS 畫素應該由視覺稿中的尺寸除以 2,使佈局視口寬度 375 中 400/2 x 400/2 與 視覺稿寬 750 中 400 x 400 的元素比例一致

如果視覺稿選擇畫布寬 375,並且 scale=1/dpr,那元素 CSS 畫素應該由視覺稿中的尺寸乘以 2,使佈局視口寬度 750 中的元素 200x2 x 200x2 與 視覺稿寬 375 中 400 x 400 的元素比例一致
其他情況,元素 CSS 畫素值就是視覺稿中的值。

知道這個之後,再決定 px 與 rem 的換算值:HTML 元素的 font-size,然後再寫 CSS 樣式(rem)。
舉幾個例子:

如果視覺稿選擇畫布寬 750,並且 scale=1,HTML 元素的 font-size = 375 / 7.5 = 50,400/2/50= 4rem

img{
    width: 4rem;
    height: 4rem;
}
複製程式碼

如果視覺稿選擇畫布寬 375,並且 scale=1/dpr,HTML 元素的 font-size = 750 / 7.5 = 100,200 * 2 /100 = 4rem

img{
	  width: 4rem;
	  height: 4rem;
  }
複製程式碼

畫布寬 750 & scale=1/dpr,HTML 元素的 font-size = 750 / 7.5 = 100,400 / 100 = 4rem

img{
	  width: 4rem;
	  height: 4rem;
  }
複製程式碼

畫布寬 375 & scale=1,HTML 元素的 font-size = 375 / 7.5 = 50,200 / 50 = 4rem

img{
	  width: 4rem;
	  height: 4rem;
  }
複製程式碼

總結

1、設定 HTML 元素的 font-size 與佈局視口寬度成比例,CSS 樣式使用相對單位 rem,元素尺寸也就與佈局視口寬度成比例,從而在每個裝置佈局一致。用視覺稿來確定這個比例值就能與視覺稿的佈局一致。

2、 設定縮放比例為 1/dpr 可以解決 1px 邊框問題。
 
為了 rem 與 px 換算方便,選定的比例值可以讓 HTML 元素的 font-size 為 100px。
iphone6 就是 7.5,iphone5 就是 6.4,不同基準的視覺稿 rem 與 px 換算比例都是 100.
最後,如果不想動態適配的內容,可以直接使用 px 單位,比如字型,還有1px邊框。

淘寶和網易的做法

這裡講的適配方案是今天 [2017-08-11] 的,以後網站方案可能會有改動,所以我把程式碼儲存下來了,可以看適配程式碼
**

手機淘寶

淘寶統一定為 10,iphone6 是 1rem = 75px,iphone5 1rem = 64px,可以到手機淘寶驗證。不同基準的視覺稿 rem 與 px 換算比例不同。

網易

並沒有做縮放,只使用了相對單位 rem。

HTML 的 font-size = 佈局視口寬度/7.5 = 裝置螢幕寬度 / 7.5
複製程式碼

在 iphone6 上,HTML 的 font-size 為 375/7.5 = 50px,如果他們的視覺稿是 1 倍圖,那就是直接除以 50 來寫 CSS 的,如果是二倍圖,就是除以 100 來寫 CSS 的。

這個比例可以隨便設定,根據實際的需求,網易設定 7.5 時考慮換算簡單,淘寶設定 10 則是考慮相容 vw。

總結

動態修改 HTML 元素的 font-size 和動態修改 viewport 在很多安卓裝置上有相容性問題。現在越來越多的瀏覽器支援 vw、vh,viewport+rem 的方案不一定是最好的方案。

對於 1px 邊框的問題,也有很多其他的解決方案。

  • 判斷終端型別是否支援 0.5px,支援則直接定義邊框為 0.5px
  • 使用 CSS 定義樣式:-webkit-transform: scale(0.5),對邊框進行縮放
  • 使用 CSS 實現陰影代替邊框:-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5)
  • 使用 CSS 定義背景樣式(background-image)來實現邊框

相關文章