轉載:http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html
作者:大漠
曾幾何時為了兼容IE低版本瀏覽器而頭痛,以為到Mobile時代可以跟這些麻煩說拜拜。可沒想到到了移動時代,為了處理各終端的適配而亂了手腳。對於混跡各社區的偶,時常發現大家拿手機淘寶的H5頁面做討論——手淘的H5頁面是如何實現多終端的適配?
那么趁此Amfe阿里無線前端團隊雙11技術連載之際,用一個實戰案例來告訴大家,手淘的H5頁面是如何實現多終端適配的,希望這篇文章對大家在Mobile的世界中能過得更輕松。
目標
拿一個雙11的Mobile頁面來做案例,比如你實現一個類似下圖的一個H5頁面:
目標很清晰,就是做一個這樣的H5頁面。
DEMO
請用手機掃下面的二維碼
痛點
雖然H5的頁面與PC的Web頁面相比簡單了不少,但讓我們頭痛的事情是要想盡辦法讓頁面能適配眾多不同的終端設備。看看下圖你就會知道,這是多么痛苦的一件事情:
點擊這里查看更多終端設備的參數。
再來看看手淘H5要適配的終端設備數據:
看到這些數據,是否死的心都有了,或者說為此捏了一把汗出來。
手淘團隊適配協作模式
早期移動端開發,對於終端設備適配問題只屬於Android系列,只不過很多設計師常常忽略Android適配問題,只出一套iOS平台設計稿。但隨着iPhone6,iPhone6+的出現,從此終端適配問題不再是Android系列了,也從這個時候讓移動端適配全面進入到“雜屏”時代。
上圖來自於paintcodeapp.com
為了應對這多么的終端設備,設計師和前端開發之間又應該采用什么協作模式?或許大家對此也非常感興趣。
而整個手淘設計師和前端開發的適配協作基本思路是:
- 選擇一種尺寸作為設計和開發基准
- 定義一套適配規則,自動適配剩下的兩種尺寸(其實不僅這兩種,你懂的)
- 特殊適配效果給出設計效果
還是上一張圖吧,因為一圖勝過千言萬語:
在此也不做更多的闡述。在手淘的設計師和前端開發協作過程中:手淘設計師常選擇iPhone6作為基准設計尺寸,交付給前端的設計尺寸是按750px * 1334px
為准(高度會隨着內容多少而改變)。前端開發人員通過一套適配規則自動適配到其他的尺寸。
根據上面所說的,設計師給我們的設計圖是一個750px * 1600px
的頁面:
前端開發完成終端適配方案
拿到設計師給的設計圖之后,剩下的事情是前端開發人員的事了。而手淘經過多年的摸索和實戰,總結了一套移動端適配的方案——flexible方案。
這種方案具體在實際開發中如何使用,暫時先賣個關子,在繼續詳細的開發實施之前,我們要先了解一些基本概念。
一些基本概念
在進行具體實戰之前,首先得了解下面這些基本概念(術語):
視窗 viewport
簡單的理解,viewport是嚴格等於瀏覽器的窗口。在桌面瀏覽器中,viewport就是瀏覽器窗口的寬度高度。但在移動端設備上就有點復雜。
移動端的viewport太窄,為了能更好為CSS布局服務,所以提供了兩個viewport:虛擬的viewportvisualviewport和布局的viewportlayoutviewport。
George Cummins在Stack Overflow上對這兩個基本概念做了詳細的解釋。
而事實上viewport是一個很復雜的知識點,上面的簡單描述可能無法幫助你更好的理解viewport,而你又想對此做更深的了解,可以閱讀PPK寫的相關教程。
物理像素(physical pixel)
物理像素又被稱為設備像素,他是顯示設備中一個最微小的物理部件。每個像素可以根據操作系統設置自己的顏色和亮度。正是這些設備像素的微小距離欺騙了我們肉眼看到的圖像效果。
設備獨立像素(density-independent pixel)
設備獨立像素也稱為密度無關像素,可以認為是計算機坐標系統中的一個點,這個點代表一個可以由程序使用的虛擬像素(比如說CSS像素),然后由相關系統轉換為物理像素。
CSS像素
CSS像素是一個抽像的單位,主要使用在瀏覽器上,用來精確度量Web頁面上的內容。一般情況之下,CSS像素稱為與設備無關的像素(device-independent pixel),簡稱DIPs。
屏幕密度
屏幕密度是指一個設備表面上存在的像素數量,它通常以每英寸有多少像素來計算(PPI)。
設備像素比(device pixel ratio)
設備像素比簡稱為dpr,其定義了物理像素和設備獨立像素的對應關系。它的值可以按下面的公式計算得到:
設備像素比 = 物理像素 / 設備獨立像素
在JavaScript中,可以通過window.devicePixelRatio
獲取到當前設備的dpr。而在CSS中,可以通過-webkit-device-pixel-ratio
,-webkit-min-device-pixel-ratio
和 -webkit-max-device-pixel-ratio
進行媒體查詢,對不同dpr的設備,做一些樣式適配(這里只針對webkit內核的瀏覽器和webview)。
dip或dp,(device independent pixels,設備獨立像素)與屏幕密度有關。dip可以用來輔助區分視網膜設備還是非視網膜設備。
縮合上述的幾個概念,用一張圖來解釋:
眾所周知,iPhone6的設備寬度和高度為375pt * 667pt
,可以理解為設備的獨立像素;而其dpr為2
,根據上面公式,我們可以很輕松得知其物理像素為750pt * 1334pt
。
如下圖所示,某元素的CSS樣式:
width: 2px; height: 2px;
在不同的屏幕上,CSS像素所呈現的物理尺寸是一致的,而不同的是CSS像素所對應的物理像素具數是不一致的。在普通屏幕下1
個CSS像素對應1
個物理像素,而在Retina屏幕下,1
個CSS像素對應的卻是4
個物理像素。
有關於更多的介紹可以點擊這里詳細了解。
看到這里,你能感覺到,在移動端時代屏幕適配除了Layout之外,還要考慮到圖片的適配,因為其直接影響到頁面顯示質量,對於如何實現圖片適配,再此不做過多詳細闡述。這里盜用了@南宮瑞揚根據mir.aculo.us翻譯的一張信息圖:
meta標簽
<meta>
標簽有很多種,而這里要着重說的是viewport的meta
標簽,其主要用來告訴瀏覽器如何規范的渲染Web頁面,而你則需要告訴它視窗有多大。在開發移動端頁面,我們需要設置meta
標簽如下:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
代碼以顯示網頁的屏幕寬度定義了視窗寬度。網頁的比例和最大比例被設置為100%。
留個懸念,因為后面的解決方案中需要重度依賴meta
標簽。
CSS單位rem
在W3C規范中是這樣描述rem
的:
font size of the root element.
簡單的理解,rem
就是相對於根元素<html>
的font-size
來做計算。而我們的方案中使用rem
單位,是能輕易的根據<html>
的font-size
計算出元素的盒模型大小。而這個特色對我們來說是特別的有益處。
前端實現方案
了解了前面一些相關概念之后,接下來我們來看實際解決方案。在整個手淘團隊,我們有一個名叫lib-flexible
的庫,而這個庫就是用來解決H5頁面終端適配的。
lib-flexible是什么?
lib-flexible
是一個制作H5適配的開源庫,可以點擊這里下載相關文件,獲取需要的JavaScript和CSS文件。
當然你可以直接使用阿里CDN:
<script src="http://g.tbcdn.cn/mtb/lib-flexible/{{version}}/??flexible_css.js,flexible.js"></script>
將代碼中的{{version}}
換成對應的版本號0.3.4
。
使用方法
lib-flexible
庫的使用方法非常的簡單,只需要在Web頁面的<head></head>
中添加對應的flexible_css.js,flexible.js
文件:
第一種方法是將文件下載到你的項目中,然后通過相對路徑添加:
<script src="build/flexible_css.debug.js"></script> <script src="build/flexible.debug.js"></script>
或者直接加載阿里CDN的文件:
<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
另外強烈建議對JS做內聯處理,在所有資源加載之前執行這個JS。執行這個JS后,會在<html>
元素上增加一個data-dpr
屬性,以及一個font-size
樣式。JS會根據不同的設備添加不同的data-dpr
值,比如說2
或者3
,同時會給html
加上對應的font-size
的值,比如說75px
。
如此一來,頁面中的元素,都可以通過rem
單位來設置。他們會根據html
元素的font-size
值做相應的計算,從而實現屏幕的適配效果。
除此之外,在引入lib-flexible
需要執行的JS之前,可以手動設置meta
來控制dpr
值,如:
<meta name="flexible" content="initial-dpr=2" />
其中initial-dpr
會把dpr
強制設置為給定的值。如果手動設置了dpr
之后,不管設備是多少的dpr
,都會強制認為其dpr
是你設置的值。在此不建議手動強制設置dpr
,因為在Flexible中,只對iOS設備進行dpr
的判斷,對於Android系列,始終認為其dpr
為1
。
if (!dpr && !scale) { 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; }
flexible的實質
flexible
實際上就是能過JS來動態改寫meta
標簽,代碼類似這樣:
var metaEl = doc.createElement('meta'); var scale = isRetina ? 0.5:1; metaEl.setAttribute('name', 'viewport'); metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); if (docEl.firstElementChild) { document.documentElement.firstElementChild.appendChild(metaEl); } else { var wrap = doc.createElement('div'); wrap.appendChild(metaEl); documen.write(wrap.innerHTML); }
事實上他做了這幾樣事情:
- 動態改寫
<meta>
標簽 - 給
<html>
元素添加data-dpr
屬性,並且動態改寫data-dpr
的值 - 給
<html>
元素添加font-size
屬性,並且動態改寫font-size
的值
案例實戰
了解Flexible相關的知識之后,咱們回到文章開頭。我們的目標是制作一個適配各終端的H5頁面。別的不多說,動手才能豐衣足食。
創建HTML模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta content="yes" name="apple-mobile-web-app-capable"> <meta content="yes" name="apple-touch-fullscreen"> <meta content="telephone=no,email=no" name="format-detection"> <script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script> <link rel="apple-touch-icon" href="favicon.png"> <link rel="Shortcut Icon" href="favicon.png" type="image/x-icon"> <title>再來一波</title> </head> <body> <!-- 頁面結構寫在這里 --> </body> </html>
正如前面所介紹的一樣,首先加載了Flexible所需的配置:
<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
這個時候可以根據設計的圖需求,在HTML文檔的<body></body>
中添加對應的HTML結構,比如:
<div class="item-section" data-repeat="sections"> <div class="item-section_header"> <h2><img src="{brannerImag}" alt=""></h2> </div> <ul> <li data-repeat="items" class="flag" role="link" href="{itemLink}"> <a class="figure flag-item" href="{itemLink}"> <img src="{imgSrc}" alt=""> </a> <div class="figcaption flag-item"> <div class="flag-title"><a href="{itemLink}" title="">{poductName}</a></div> <div class="flag-price"><span>雙11價</span><strong>¥{price}</strong><small>({preferential})</small></div> <div class="flag-type">{activityType}</div> <a class="flag-btn" href="{shopLink}">{activeName}</a> </div> </li> </ul> </div>
這僅是一個示例文檔,大家可以根據自己風格寫模板。
為了能更好的測試頁面,給其配置一點假數據:
//define data var pageData = { sections:[{ "brannerImag":"http://xxx.cdn.com/B1PNLZKXXXXXaTXXXXXXXXXXXX-750-481.jpg", items:[{ "itemLink": "##", "imgSrc": "https://placeimg.com/350/350/people/grayscale", "poductName":"Carter's1年式灰色長袖連體衣包腳爬服全棉鯨魚男嬰兒童裝115G093", "price": "299.06", "preferential": "滿400減100", "activityType": "1小時內熱賣5885件", "shopLink":"##", "activeName": "馬上搶!" } .... }] }] }
接下來的工作就是美化工作了。在寫具體樣式之前,有幾個點需要先了解一下。
把視覺稿中的px
轉換成rem
讀到這里,大家應該都知道,我們接下來要做的事情,就是如何把視覺稿中的px
轉換成rem
。在此花點時間解釋一下。
首先,目前日常工作當中,視覺設計師給到前端開發人員手中的視覺稿尺寸一般是基於640px
、750px
以及1125px
寬度為准。甚至為什么?大家應該懂的(考慮Retina屏)。
正如文章開頭顯示的示例設計稿,他就是一張以750px
為基礎設計的。那么問題來了,我們如何將設計稿中的各元素的px
轉換成rem
。
我廠的視覺設計師想得還是很周到的,會幫你把相關的信息在視覺稿上標注出來。
目前Flexible會將視覺稿分成100份
(主要為了以后能更好的兼容vh
和vw
),而每一份被稱為一個單位a
。同時1rem
單位被認定為10a
。針對我們這份視覺稿可以計算出:
1a = 7.5px 1rem = 75px
那么我們這個示例的稿子就分成了10a
,也就是整個寬度為10rem
,<html>
對應的font-size
為75px
:
這樣一來,對於視覺稿上的元素尺寸換算,只需要原始的px值
除以rem基准值
即可。例如此例視覺稿中的圖片,其尺寸是176px * 176px
,轉換成為2.346667rem * 2.346667rem
。
如何快速計算
在實際生產當中,如果每一次計算px
轉換rem
,或許會覺得非常麻煩,或許直接影響大家平時的開發效率。為了能讓大家更快進行轉換,我們團隊內的同學各施所長,為px
轉換rem
寫了各式各樣的小工具。
CSSREM
CSSREM是一個CSS的px
值轉rem
值的Sublime Text3自動完成插件。這個插件是由@正霖編寫。先來看看插件的效果:
有關於CSSREM如何安裝、配置教程可以點擊這里查閱。
CSS處理器
除了使用編輯器的插件之外,還可以使用CSS的處理器來幫助大家處理。比如說Sass、LESS以及PostCSS這樣的處理器。我們簡單來看兩個示例。
Sass
使用Sass的同學,可以使用Sass的函數、混合宏這些功能來實現:
@function px2em($px, $base-font-size: 16px) { @if (unitless($px)) { @warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels for you"; @return px2em($px + 0px); // That may fail. } @else if (unit($px) == em) { @return $px; } @return ($px / $base-font-size) * 1em; }
除了使用Sass函數外,還可以使用Sass的混合宏:
@mixin px2rem($property,$px-values,$baseline-px:16px,$support-for-ie:false){ //Conver the baseline into rems $baseline-rem: $baseline-px / 1rem * 1; //Print the first line in pixel values @if $support-for-ie { #{$property}: $px-values; } //if there is only one (numeric) value, return the property/value line for it. @if type-of($px-values) == "number"{ #{$property}: $px-values / $baseline-rem; } @else { //Create an empty list that we can dump values into $rem-values:(); @each $value in $px-values{ // If the value is zero or not a number, return it @if $value == 0 or type-of($value) != "number"{ $rem-values: append($rem-values, $value / $baseline-rem); } } // Return the property and its list of converted values #{$property}: $rem-values; } }
有關於更多的介紹,可以點擊這里進行了解。
PostCSS(px2rem)
除了Sass這樣的CSS處理器這外,我們團隊的@頌奇同學還開發了一款npm
的工具px2rem。安裝好px2rem之后,可以在項目中直接使用。也可以使用PostCSS。使用PostCSS插件postcss-px2rem:
var gulp = require('gulp'); var postcss = require('gulp-postcss'); var px2rem = require('postcss-px2rem'); gulp.task('default', function() { var processors = [px2rem({remUnit: 75})]; return gulp.src('./src/*.css') .pipe(postcss(processors)) .pipe(gulp.dest('./dest')); });
除了在Gulp中配置外,還可以使用其他的配置方式,詳細的介紹可以點擊這里進行了解。
配置完成之后,在實際使用時,你只要像下面這樣使用:
.selector { width: 150px; height: 64px; /*px*/ font-size: 28px; /*px*/ border: 1px solid #ddd; /*no*/ }
px2rem
處理之后將會變成:
.selector { width: 2rem; border: 1px solid #ddd; } [data-dpr="1"] .selector { height: 32px; font-size: 14px; } [data-dpr="2"] .selector { height: 64px; font-size: 28px; } [data-dpr="3"] .selector { height: 96px; font-size: 42px; }
在整個開發中有了這些工具之后,完全不用擔心px
值轉rem
值影響開發效率。
字號不使用rem
前面大家都見證了如何使用rem
來完成H5適配。那么文本又將如何處理適配。是不是也通過rem
來做自動適配。
顯然,我們在iPhone3G和iPhone4的Retina屏下面,希望看到的文本字號是相同的。也就是說,我們不希望文本在Retina屏幕下變小,另外,我們希望在大屏手機上看到更多文本,以及,現在絕大多數的字體文件都自帶一些點陣尺寸,通常是16px
和24px
,所以我們不希望出現13px
和15px
這樣的奇葩尺寸。
如此一來,就決定了在制作H5的頁面中,rem
並不適合用到段落文本上。所以在Flexible整個適配方案中,考慮文本還是使用px
作為單位。只不過使用[data-dpr]
屬性來區分不同dpr
下的文本字號大小。
div {
width: 1rem; height: 0.4rem; font-size: 12px; // 默認寫上dpr為1的fontSize } [data-dpr="2"] div { font-size: 24px; } [data-dpr="3"] div { font-size: 36px; }
為了能更好的利於開發,在實際開發中,我們可以定制一個font-dpr()
這樣的Sass混合宏:
@mixin font-dpr($font-size){ font-size: $font-size; [data-dpr="2"] & { font-size: $font-size * 2; } [data-dpr="3"] & { font-size: $font-size * 3; } }
有了這樣的混合宏之后,在開發中可以直接這樣使用:
@include font-dpr(16px);
當然這只是針對於描述性的文本,比如說段落文本。但有的時候文本的字號也需要分場景的,比如在項目中有一個slogan,業務方希望這個slogan能根據不同的終端適配。針對這樣的場景,完全可以使用rem
給slogan做計量單位。
CSS
本來想把這個頁面的用到的CSS(或SCSS)貼出來,但考慮篇幅過長,而且這么簡單的頁面,我想大家也能輕而易舉搞定。所以就省略了。權當是給大家留的一個作業吧,感興趣的可以試試Flexible能否幫你快速完成H5頁面終端適配。
效果
最后來看看真機上顯示的效果吧。我截了兩種設備下的效果:
iPhone4
iPhone6+
總結
其實H5適配的方案有很多種,網上有關於這方面的教程也非常的多。不管哪種方法,都有其自己的優勢和劣勢。而本文主要介紹的是如何使用Flexible這樣的一庫來完成H5頁面的終端適配。為什么推薦使用Flexible庫來做H5頁面的終端設備適配呢?主要因為這個庫在手淘已經使用了近一年,而且已達到了較為穩定的狀態。除此之外,你不需要考慮如何對元素進行折算,可以根據對應的視覺稿,直接切入。
當然,如果您有更好的H5頁面終端適配方案,歡迎在下面的評論中與我們一起分享。如果您在使用這個庫時,碰到任何問題,都可以在Github給我們提Issue。我們團隊會努力解決相關需Issues。
更新
同學們反饋需要一個在線演示的DEMO。那么花了點時間寫了個demo,希望對有需要的同學有所幫助。友情提示:DEMO未經過所有設備測試,可能在部分設備上有細節上的差異
DEMO
請用手機掃下面的二維碼
更新【2016年01月13日】
首先,由衷的感謝@完顏 幫忙踩了這個坑,回想起iOS從7~8,從8~9,都踩過只至少一個坑,真的也是醉了。
手淘這邊的flexible方案臨時升級如下:
- 針對OS 9_3的UA,做臨時處理,強制
dpr
為1
,即scale
也為1
,雖然犧牲了這些版本上的高清方案,但是也只能這么處理了 - 這個版本不打算發布到CDN(也不發不到tnpm),所以大家更新的方式,最好手動復制代碼內聯到
html
中,具體代碼可以點擊這里下載