使用Flexible實現手淘H5頁面的終端適配rem自適應佈局-移動端自適應必備
曾幾何時為了相容IE低版本瀏覽器而頭痛,以為到Mobile時代可以跟這些麻煩說拜拜。可沒想到到了移動時代,為了處理各終端的適配而亂了手腳。對於混跡各社群的偶,時常發現大家拿手機淘寶的H5頁面做討論——手淘的H5頁面是如何實現多終端的適配?
那麼趁此Amfe阿里無線前端團隊雙11技術連載之際,用一個實戰案例來告訴大家,手淘的H5頁面是如何實現多終端適配的,希望這篇文章對大家在Mobile的世界中能過得更輕鬆。
目標
拿一個雙11的Mobile頁面來做案例,比如你實現一個類似下圖的一個H5頁面:
目標很清晰,就是做一個這樣的H5頁面。
DEMO
請用手機掃下面的二維碼
痛點
雖然H5的頁面與PC的Web頁面相比簡單了不少,但讓我們頭痛的事情是要想盡辦法讓頁面能適配眾多不同的終端裝置。看看下圖你就會知道,這是多麼痛苦的一件事情:
點選這裡檢視更多終端裝置的引數。
再來看看手淘H5要適配的終端裝置資料:
看到這些資料,是否死的心都有了,或者說為此捏了一把汗出來。
手淘團隊適配協作模式
早期移動端開發,對於終端裝置適配問題只屬於Android系列,只不過很多設計師常常忽略Android適配問題,只出一套iOS平臺設計稿。但隨著iPhone6,iPhone6+的出現,從此終端適配問題不再是Android系列了,也從這個時候讓移動端適配全面進入到“雜屏”時代。
為了應對這多麼的終端裝置,設計師和前端開發之間又應該採用什麼協作模式?或許大家對此也非常感興趣。
而整個手淘設計師和前端開發的適配協作基本思路是:
- 選擇一種尺寸作為設計和開發基準
- 定義一套適配規則,自動適配剩下的兩種尺寸(其實不僅這兩種,你懂的)
- 特殊適配效果給出設計效果
還是上一張圖吧,因為一圖勝過千言萬語:
在此也不做更多的闡述。在手淘的設計師和前端開發協作過程中:手淘設計師常選擇iPhone6作為基準設計尺寸,交付給前端的設計尺寸是按750px * 1334px
為準(高度會隨著內容多少而改變)。前端開發人員通過一套適配規則自動適配到其他的尺寸。
根據上面所說的,設計師給我們的設計圖是一個750px * 1600px
前端開發完成終端適配方案
拿到設計師給的設計圖之後,剩下的事情是前端開發人員的事了。而手淘經過多年的摸索和實戰,總結了一套移動端適配的方案——。
這種方案具體在實際開發中如何使用,暫時先賣個關子,在繼續詳細的開發實施之前,我們要先了解一些基本概念。
一些基本概念
在進行具體實戰之前,首先得了解下面這些基本概念(術語):
視窗 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頁面終端適配方案,歡迎在下面的評論中與我