(淘寶無限適配)手機端rem佈局詳解
從網易與淘寶的font-size思考前端設計稿與工作流
本文結合自己對網易與淘寶移動端首頁html元素上的font-size這個屬性的思考與學習,討論html5設計稿尺寸以及前端與設計之間協作流程的問題,內容較多,但對你的技術和工作一定有價值,歡迎閱讀和點評:)。
這是淘寶的github網址,裡面有適配所需的js還有說明文件 地址:https://github.com/amfe/lib-flexible
1. 問題的引出
最近閱讀白樹的博文《移動web資源整理》時,他在博文中有一段指出,如果html5要適應各種解析度的移動裝置,應該使用rem這樣的尺寸單位,同時給出了一段針對各個解析度範圍在html上設定font-size的程式碼:
html{font-size:10px} @media screen and (min-width:321px) and (max-width:375px){html{font-size:11px}} @media screen and (min-width:376px) and (max-width:414px){html{font-size:12px}} @media screen and (min-width:415px) and (max-width:639px){html{font-size:15px}} @media screen and (min-width:640px) and (max-width:719px){html{font-size:20px}} @media screen and (min-width:720px) and (max-width:749px){html{font-size:22.5px}} @media screen and (min-width:750px) and (max-width:799px){html{font-size:23.5px}} @media screen and (min-width:800px){html{font-size:25px}}
在實際專案中,把與元素尺寸有關的css,如width,height,line-height,margin,padding等都以rem作為單位,這樣頁面在不同裝置下就能保持一致的網頁佈局。舉例來說,網頁有一個.item類,設定了width為3.4rem,該類在不同解析度下對應的實際寬度如下:
321px <= device-width <= 375px,font-size:11px ---> .item的width:34px 376px <= device-width <= 414px,font-size:12px ---> .item的width:37.4px 415px <= device-width <= 639px,font-size:15px ---> .item的width:40.8px 640px <= device-width <= 719px,font-size:20px ---> .item的width:51px 720px <= device-width <= 749px,font-size:22.5px ---> .item的width:76.5px 750px <= device-width <= 799px,font-size:23.5px ---> .item的width:79.8999999px 800px <= device-width ,font-size:25px ---> .item的width:85px
以上程式碼乍看沒啥問題,響應式設計不就應該是這麼幹的嗎?但是從工作量和複雜度方面來考慮,它有以下幾個不足:
- (1).item類在所有裝置下的width都是3.4rem,但在不同解析度下的實際畫素是不一樣的,所以在有些解析度下,width的介面效果不一定合適,有可能太寬,有可能太窄,這時候就要對width進行調整,那麼就需要針對.item寫媒介查詢的程式碼,為該解析度重新設計一個rem值。然而,這裡有7種媒介查詢的情況,css又有很多跟尺寸相關的屬性,哪個屬性在哪個解析度範圍不合適都是不定的,最後會導致要寫很多的媒介查詢才能適配所有裝置,而且在寫的時候rem都得根據某個解析度html的font-size去算,這個計算可不見得每次都那麼容易,比如40px / 23.5px,這個rem值口算不出來吧!由此可見這其中的麻煩有多少。
- (2)以上程式碼中給出的7個範圍下的font-size不一定是合適的,這7個範圍也不一定合適,實際有可能不需要這麼多,所以找出這些個範圍,以及每個範圍最合適的font-size也很麻煩
- (3)設計稿都是以解析度來標明尺寸的,前端在根據設計稿裡各個元素的畫素尺寸轉換為rem時,該以哪個font-size為準呢?這需要去寫才能知道。
正是因為以上提到的一些不足,我覺得這種適配方式不是特別好,寫起來太麻煩。為了完成工作,我們需要找尋更簡單更有效率的方法。那麼html5該如何去做眾多移動裝置的適配呢?我目前已知的有3種解決方法,將會在下文的第2,3,4部分闡述,如果你閱讀之後,有什麼想法,儘可在評論中與我交流。
2. 簡單問題簡單解決
我覺得有些web app並一定很複雜,比如拉勾網,你看看它的頁面在iphone4,iphone6,ipad下的樣子就知道了:
它的頁面有一個特點,就是:
- 頂部與底部的bar不管解析度怎麼變,它的高度和位置都不變
- 中間每條招聘資訊不管解析度怎麼變,招聘公司的圖示等資訊都位於條目的左邊,薪資都位於右邊
這種app是一種典型的彈性佈局:關鍵元素高寬和位置都不變,只有容器元素在做伸縮變換。對於這類app,記住一個開發原則就好:文字流式,控制元件彈性,圖片等比縮放。以圖描述:
這個規則是一套基本的適配規則,對於這種簡單app來說已經足夠,同時它也是後面要說的rem佈局的基礎。另外對於拉勾這種app可能需要額外媒介查詢對佈局進行調整的就是小螢幕裝置。舉例來說,因為現在很多設計稿是根據iphone6的尺寸來的,而iphon6裝置寬的邏輯的畫素是375px,而iphone4的邏輯畫素是320個畫素,所以如果你根據設計稿做出來的東西,在iphone4裡面可能顯示不下,比如說拉鉤網底部那個下載框,你對比看下就知道了,這是4:
這是6:
6下面兩邊的間距比4多很多,說明拉勾對4肯定是做過適配的,從程式碼也可以證實這一點:
不過如果你拿到的是根據4的設計稿,那就沒有問題,比4解析度大的裝置肯定能顯示根據4的尺寸做出來的東西。
還有一點,這種情況css尺寸單位用px就好,不要用rem,避免增加複雜度。
3. 網易的做法
先來看看網易在不同解析度下,呈現的效果:
從上面幾張圖可以看出,隨著解析度的增大,頁面的效果會發生明顯變化,主要體現在各個元素的寬高與間距。375*680的比320*680的導航欄明顯要高。能夠達到這種效果的根本原因就是因為網易頁面裡除了font-size之外的其它css尺寸都使用了rem作為單位,比如你看導航欄的高度設定程式碼:
可是在本文第1部分提到,使用rem佈局結合在html上根據不同解析度設定不同font-size有很多不好解決的麻煩,網易是如何解決的呢?最根本的原因在於,網易頁面上html的font-size不是預先通過媒介查詢在css裡定義好的,而是通過js計算出來的,所以當解析度發生變化時,html的font-size就會變,不過這得在你調整解析度後,重新整理頁面才能看得到效果。你看程式碼就知道為啥font-size是直接寫到html的style上面的了(js設定的原因):
它是根據什麼計算的,這就跟設計稿有關了,拿網易來說,它的設計稿應該是基於iphone4或者iphone5來的,所以它的設計稿豎直放時的橫向解析度為640px,為了計算方便,取一個100px的font-size為參照,那麼body元素的寬度就可以設定為width: 6.4rem,於是html的font-size=deviceWidth / 6.4。這個deviceWidth就是viewport設定中的那個deviceWidth。根據這個計算規則,可得出本部分開始的四張截圖中html的font-size大小如下:
deviceWidth = 320,font-size = 320 / 6.4 = 50px deviceWidth = 375,font-size = 375 / 6.4 = 58.59375px deviceWidth = 414,font-size = 414 / 6.4 = 64.6875px deviceWidth = 500,font-size = 500 / 6.4 = 78.125px
事實上網易就是這麼幹的,你看它的程式碼就知道,body元素的寬是:
根據這個可以肯定它的設計稿豎著時的橫向解析度為640。然後你再看看網易在解析度為320*680,375*680,414*680,500*680時,html的font-size是不是與上面計算的一致:
320*680
375*680
414*680
500*680
這個deviceWidth通過document.documentElement.clientWidth就能取到了,所以當頁面的dom ready後,做的第一件事情就是:
document.documentElement.style.fontSize = document.documentElement.clientWidth / 6.4 + 'px';
這個6.4怎麼來的,當然是根據設計稿的橫向解析度/100得來的。下面總結下網易的這種做法:
- (1)先拿設計稿豎著的橫向解析度除以100得到body元素的寬度:
如果設計稿基於iphone6,橫向解析度為750,body的width為750 / 100 = 7.5rem 如果設計稿基於iphone4/5,橫向解析度為640,body的width為640 / 100 = 6.4rem
- (2)佈局時,設計圖標註的尺寸除以100得到css中的尺寸,比如下圖:
- 播放器高度為210px,寫樣式的時候css應該這麼寫:height: 2.1rem。之所以取一個100作為參照,就是為了這裡計算rem的方便!
- (3)在dom ready以後,通過以下程式碼設定html的font-size:
document.documentElement.style.fontSize = document.documentElement.clientWidth / 6.4 + 'px';
- 6.4只是舉個例子,如果是750的設計稿,應該除以7.5。
- (4)font-size可能需要額外的媒介查詢,並且font-size不能使用rem,如網易的設定:
@media screen and (max-width:321px){ .m-navlist{font-size:15px} } @media screen and (min-width:321px) and (max-width:400px){ .m-navlist{font-size:16px} } @media screen and (min-width:400px){ .m-navlist{font-size:18px} }
最後還有2個情況要說明:
第一,如果採用網易這種做法,視口要如下設定:
<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
第二,當deviceWidth大於設計稿的橫向解析度時,html的font-size始終等於橫向解析度/body元素寬:
640*680
641*680
之所以這麼幹,是因為當deviceWidth大於640時,則物理解析度大於1280(這就看裝置的devicePixelRatio這個值了),應該去訪問pc網站了。事實就是這樣,你從手機訪問網易,看到的是觸屏版的頁面,如果從pad訪問,看到的就是電腦版的頁面。如果你也想這麼幹,只要把總結中第三步的程式碼稍微改一下就行了:
var deviceWidth = document.documentElement.clientWidth; if(deviceWidth > 640) deviceWidth = 640; document.documentElement.style.fontSize = deviceWidth / 6.4 + 'px';
4. 淘寶的做法
看看淘寶在不同解析度下,呈現的效果:
淘寶的效果跟網易的效果其實是類似的,隨著解析度的變化,頁面元素的尺寸和間距都相應變化,這是因為淘寶的尺寸也是使用了rem的原因。在介紹它的做法之前,先來了解一點關於viewport的知識,通常我們採用如下程式碼設定viewport:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
這樣整個網頁在裝置內顯示時的頁面寬度就會等於裝置邏輯畫素大小,也就是device-width。這個device-width的計算公式為:
裝置的物理解析度/(devicePixelRatio * scale),在scale為1的情況下,device-width = 裝置的物理解析度/devicePixelRatio 。
devicePixelRatio稱為裝置畫素比,每款裝置的devicePixelRatio都是已知,並且不變的,目前高清屏,普遍都是2,不過還有更高的,比如2.5, 3 等,我魅族note的手機的devicePixelRatio就是3。淘寶觸屏版佈局的前提就是viewport的scale根據devicePixelRatio動態設定:
在devicePixelRatio為2的時候,scale為0.5
在devicePixelRatio為3的時候,scale為0.3333
這麼做目的當然是為了保證頁面的大小與設計稿保持一致了,比如設計稿如果是750的橫向解析度,那麼實際頁面的device-width,以iphone6來說,也等於750,這樣的話設計稿上標註的尺寸只要除以某一個值就能夠轉換為rem了。通過js設定viewport的方法如下:
var scale = 1 / devicePixelRatio; document.querySelector('meta[name="viewport"]').setAttribute('content','initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
淘寶佈局的第二個要點,就是html元素的font-size的計算公式,font-size = deviceWidth / 10:
接下來要解決的問題是,元素的尺寸該如何計算,比如說設計稿上某一個元素的寬為150px,換算成rem應該怎麼算呢?這個值等於設計稿標註尺寸/該設計稿對應的html的font-size。拿淘寶來說的,他們用的設計稿是750的,所以html的font-size就是75,如果某個元素時150px的寬,換算成rem就是150 / 75 = 2rem。總結下淘寶的這些做法:
- (1)動態設定viewport的scale
var scale = 1 / devicePixelRatio; document.querySelector('meta[name="viewport"]').setAttribute('content','initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
- (2)動態計算html的font-size
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
- (3)佈局的時候,各元素的css尺寸=設計稿標註尺寸/設計稿橫向解析度/10
- (4)font-size可能需要額外的媒介查詢,並且font-size不使用rem,這一點跟網易是一樣的。
最後還有一個情況要說明,跟網易一樣,淘寶也設定了一個臨界點,當裝置豎著時橫向物理解析度大於1080時,html的font-size就不會變化了,原因也是一樣的,解析度已經可以去訪問電腦版頁面了。
關於這種做法的具體實現,淘寶已經給我們提供了一個開源的解決方案,具體請檢視:
之前沒有找到這相關的資料,實在不好意思:(
5. 比較網易與淘寶的做法
共同點:
- 都能適配所有的手機裝置,對於pad,網易與淘寶都會跳轉到pc頁面,不再使用觸屏版的頁面
- 都需要動態設定html的font-size
- 佈局時各元素的尺寸值都是根據設計稿標註的尺寸計算出來,由於html的font-size是動態調整的,所以能夠做到不同解析度下頁面佈局呈現等比變化
- 容器元素的font-size都不用rem,需要額外地對font-size做媒介查詢
- 都能應用於尺寸不同的設計稿,只要按以上總結的方法去用就可以了
不同點
- 淘寶的設計稿是基於750的橫向解析度,網易的設計稿是基於640的橫向解析度,還要強調的是,雖然設計稿不同,但是最終的結果是一致的,設計稿的尺寸一個公司設計人員的工作標準,每個公司不一樣而已
- 淘寶還需要動態設定viewport的scale,網易不用
- 最重要的區別就是:網易的做法,rem值很好計算,淘寶的做法肯定得用計算器才能用好了 。不過要是你使用了less和sass這樣的css處理器,就好辦多了,以淘寶跟less舉例,我們可以這樣編寫less:
//定義一個變數和一個mixin
@baseFontSize: 75;//基於視覺稿橫屏尺寸/100得出的基準font-size .px2rem(@name, @px){ @{name}: @px / @baseFontSize * 1rem; }
//使用示例:
.container { .px2rem(height, 240); }
//less翻譯結果: .container { height: 3.2rem; }
6. 如何與設計協作
前端與設計師的協作應該是比較簡單的,最重要的是要規範設計提供給你的產物,通常對於前端來說,我們需要設計師提供標註尺寸後的設計稿以及各種元素的切圖檔案,有了這些就可以開始佈局了。考慮到Retina顯示屏以及這麼多移動裝置解析度卻不一樣的問題,那麼設計師應該提供多套設計稿嗎?從網易和淘寶的做法來看,應該是不用了,我們可以按照設計稿,先做出一套佈局,按照以上方法做適配,由於是等比適配,所以各個裝置的視覺效果差異應該會很小,當然也排除不了一些需要媒介查詢特殊處理的情況,這肯定避免不了的。下面這張圖是淘寶設計師分享的他們的工作流程:
解釋一下就是:
第一步,視覺設計階段,設計師按寬度750px(iPhone 6)做設計稿,除圖片外所有設計元素用向量路徑來做。設計定稿後在750px的設計稿上做標註,輸出標註圖。同時等比放大1.5倍生成寬度1125px的設計稿,在1125px的稿子裡切圖。
第二步,輸出兩個交付物給開發工程師:一個是程式用到的@3x切圖資源,另一個是寬度750px的設計標註圖。
第三步,開發工程師拿到750px標註圖和@3x切圖資源,完成iPhone 6(375pt)的介面開發。此階段不能用固定寬度的方式開發介面,得用自動佈局(auto layout),方便後續適配到其它尺寸。
第四步,適配除錯階段,基於iPhone 6的介面效果,分別向上向下除錯iPhone 6 plus(414pt)和iPhone 5S及以下(320pt)的介面效果。由此完成大中小三屏適配。
注意第三步,就要使用我們以上介紹的網易跟淘寶的適配方法了。假如公司設計稿不是基於750的怎麼辦,其實很簡單,按上圖做一些相應替換即可,但是流程和方法還是一樣的。解釋一下為什麼要在@3x的圖裡切,這是因為現在市面上也有不少像魅藍note這種超高清螢幕,devicePixelRatio已經達到3了,這個切圖保證在所有裝置都清晰顯示。
7. 總結
總算是羅裡吧嗦地把文章寫完了, 希望你還覺得滿意,這篇文章對我來說價值也很大,今後做html5的專案就有思路了,本文提到的三種方法將來肯定都有用武之地。最後,歡迎大家在評論裡與我交流你對本文的看法,我們可以一起交流,一起進步。