|
那么趁此Amfe阿里無(wú)線前端團(tuán)隊(duì)雙11技術(shù)連載之際,用一個(gè)實(shí)戰(zhàn)案例來(lái)告訴大家,手淘的H5頁(yè)面是如何實(shí)現(xiàn)多終端適配的,希望這篇文章對(duì)大家在Mobile的世界中能過(guò)得更輕松。 目標(biāo)拿一個(gè)雙11的Mobile頁(yè)面來(lái)做案例,比如你實(shí)現(xiàn)一個(gè)類似下圖的一個(gè)H5頁(yè)面:
目標(biāo)很清晰,就是做一個(gè)這樣的H5頁(yè)面。 痛點(diǎn)雖然H5的頁(yè)面與PC的Web頁(yè)面相比簡(jiǎn)單了不少,但讓我們頭痛的事情是要想盡辦法讓頁(yè)面能適配眾多不同的終端設(shè)備。看看下圖你就會(huì)知道,這是多么痛苦的一件事情: 點(diǎn)擊這里查看更多終端設(shè)備的參數(shù)。 再來(lái)看看手淘H5要適配的終端設(shè)備數(shù)據(jù):
看到這些數(shù)據(jù),是否死的心都有了,或者說(shuō)為此捏了一把汗出來(lái)。 手淘團(tuán)隊(duì)適配協(xié)作模式早期移動(dòng)端開(kāi)發(fā),對(duì)于終端設(shè)備適配問(wèn)題只屬于Android系列,只不過(guò)很多設(shè)計(jì)師常常忽略Android適配問(wèn)題,只出一套iOS平臺(tái)設(shè)計(jì)稿。但隨著iPhone6,iPhone6+的出現(xiàn),從此終端適配問(wèn)題不再是Android系列了,也從這個(gè)時(shí)候讓移動(dòng)端適配全面進(jìn)入到“雜屏”時(shí)代。
為了應(yīng)對(duì)這多么的終端設(shè)備,設(shè)計(jì)師和前端開(kāi)發(fā)之間又應(yīng)該采用什么協(xié)作模式?或許大家對(duì)此也非常感興趣。 而整個(gè)手淘設(shè)計(jì)師和前端開(kāi)發(fā)的適配協(xié)作基本思路是:
還是上一張圖吧,因?yàn)橐粓D勝過(guò)千言萬(wàn)語(yǔ):
在此也不做更多的闡述。在手淘的設(shè)計(jì)師和前端開(kāi)發(fā)協(xié)作過(guò)程中:手淘設(shè)計(jì)師常選擇iPhone6作為基準(zhǔn)設(shè)計(jì)尺寸,交付給前端的設(shè)計(jì)尺寸是按 根據(jù)上面所說(shuō)的,設(shè)計(jì)師給我們的設(shè)計(jì)圖是一個(gè)
前端開(kāi)發(fā)完成終端適配方案拿到設(shè)計(jì)師給的設(shè)計(jì)圖之后,剩下的事情是前端開(kāi)發(fā)人員的事了。而手淘經(jīng)過(guò)多年的摸索和實(shí)戰(zhàn),總結(jié)了一套移動(dòng)端適配的方案——flexible方案。 這種方案具體在實(shí)際開(kāi)發(fā)中如何使用,暫時(shí)先賣(mài)個(gè)關(guān)子,在繼續(xù)詳細(xì)的開(kāi)發(fā)實(shí)施之前,我們要先了解一些基本概念。 一些基本概念在進(jìn)行具體實(shí)戰(zhàn)之前,首先得了解下面這些基本概念(術(shù)語(yǔ)): 視窗 viewport簡(jiǎn)單的理解,viewport是嚴(yán)格等于瀏覽器的窗口。在桌面瀏覽器中,viewport就是瀏覽器窗口的寬度高度。但在移動(dòng)端設(shè)備上就有點(diǎn)復(fù)雜。 移動(dòng)端的viewport太窄,為了能更好為CSS布局服務(wù),所以提供了兩個(gè)viewport:虛擬的viewportvisualviewport和布局的viewportlayoutviewport。 George Cummins在Stack Overflow上對(duì)這兩個(gè)基本概念做了詳細(xì)的解釋。 而事實(shí)上viewport是一個(gè)很復(fù)雜的知識(shí)點(diǎn),上面的簡(jiǎn)單描述可能無(wú)法幫助你更好的理解viewport,而你又想對(duì)此做更深的了解,可以閱讀PPK寫(xiě)的相關(guān)教程。 物理像素(physical pixel)物理像素又被稱為設(shè)備像素,他是顯示設(shè)備中一個(gè)最微小的物理部件。每個(gè)像素可以根據(jù)操作系統(tǒng)設(shè)置自己的顏色和亮度。正是這些設(shè)備像素的微小距離欺騙了我們?nèi)庋劭吹降膱D像效果。
設(shè)備獨(dú)立像素(density-independent pixel)設(shè)備獨(dú)立像素也稱為密度無(wú)關(guān)像素,可以認(rèn)為是計(jì)算機(jī)坐標(biāo)系統(tǒng)中的一個(gè)點(diǎn),這個(gè)點(diǎn)代表一個(gè)可以由程序使用的虛擬像素(比如說(shuō)CSS像素),然后由相關(guān)系統(tǒng)轉(zhuǎn)換為物理像素。 CSS像素CSS像素是一個(gè)抽像的單位,主要使用在瀏覽器上,用來(lái)精確度量Web頁(yè)面上的內(nèi)容。一般情況之下,CSS像素稱為與設(shè)備無(wú)關(guān)的像素(device-independent pixel),簡(jiǎn)稱DIPs。 屏幕密度屏幕密度是指一個(gè)設(shè)備表面上存在的像素?cái)?shù)量,它通常以每英寸有多少像素來(lái)計(jì)算(PPI)。 設(shè)備像素比(device pixel ratio)設(shè)備像素比簡(jiǎn)稱為dpr,其定義了物理像素和設(shè)備獨(dú)立像素的對(duì)應(yīng)關(guān)系。它的值可以按下面的公式計(jì)算得到:
在JavaScript中,可以通過(guò) dip或dp,(device independent pixels,設(shè)備獨(dú)立像素)與屏幕密度有關(guān)。dip可以用來(lái)輔助區(qū)分視網(wǎng)膜設(shè)備還是非視網(wǎng)膜設(shè)備。 縮合上述的幾個(gè)概念,用一張圖來(lái)解釋:
眾所周知,iPhone6的設(shè)備寬度和高度為 如下圖所示,某元素的CSS樣式: CSS
在不同的屏幕上,CSS像素所呈現(xiàn)的物理尺寸是一致的,而不同的是CSS像素所對(duì)應(yīng)的物理像素具數(shù)是不一致的。在普通屏幕下 有關(guān)于更多的介紹可以點(diǎn)擊這里詳細(xì)了解。 看到這里,你能感覺(jué)到,在移動(dòng)端時(shí)代屏幕適配除了Layout之外,還要考慮到圖片的適配,因?yàn)槠渲苯佑绊懙巾?yè)面顯示質(zhì)量,對(duì)于如何實(shí)現(xiàn)圖片適配,再此不做過(guò)多詳細(xì)闡述。這里盜用了@南宮瑞揚(yáng)根據(jù)mir.翻譯的一張信息圖:
meta標(biāo)簽
XHTML
代碼以顯示網(wǎng)頁(yè)的屏幕寬度定義了視窗寬度。網(wǎng)頁(yè)的比例和最大比例被設(shè)置為100%。 留個(gè)懸念,因?yàn)楹竺娴慕鉀Q方案中需要重度依賴 CSS單位rem在W3C規(guī)范中是這樣描述
簡(jiǎn)單的理解, 前端實(shí)現(xiàn)方案了解了前面一些相關(guān)概念之后,接下來(lái)我們來(lái)看實(shí)際解決方案。在整個(gè)手淘團(tuán)隊(duì),我們有一個(gè)名叫 lib-flexible是什么?
當(dāng)然你可以直接使用阿里CDN: JavaScript
將代碼中的 使用方法
第一種方法是將文件下載到你的項(xiàng)目中,然后通過(guò)相對(duì)路徑添加: XHTML
或者直接加載阿里CDN的文件: XHTML
另外強(qiáng)烈建議對(duì)JS做內(nèi)斂處理,在所有資源加載之前執(zhí)行這個(gè)JS。執(zhí)行這個(gè)JS后,會(huì)在 如此一來(lái),頁(yè)面中的元素,都可以通過(guò) 除此之外,在引入 XHTML
其中 JavaScript
flexible的實(shí)質(zhì)
JavaScript
事實(shí)上他做了這幾樣事情:
案例實(shí)戰(zhàn)了解Flexible相關(guān)的知識(shí)之后,咱們回到文章開(kāi)頭。我們的目標(biāo)是制作一個(gè)適配各終端的H5頁(yè)面。別的不多說(shuō),動(dòng)手才能豐衣足食。 創(chuàng)建HTML模板
XHTML
正如前面所介紹的一樣,首先加載了Flexible所需的配置: XHTML
這個(gè)時(shí)候可以根據(jù)設(shè)計(jì)的圖需求,在HTML文檔的 XHTML
這僅是一個(gè)示例文檔,大家可以根據(jù)自己風(fēng)格寫(xiě)模板。 為了能更好的測(cè)試頁(yè)面,給其配置一點(diǎn)假數(shù)據(jù): JavaScript
接下來(lái)的工作就是美化工作了。在寫(xiě)具體樣式之前,有幾個(gè)點(diǎn)需要先了解一下。 把視覺(jué)稿中的
|
|
1 2 |
1a = 7.5px 1rem = 75px |
那么我們這個(gè)示例的稿子就分成了10a,也就是整個(gè)寬度為10rem,<html>對(duì)應(yīng)的font-size為75px:

這樣一來(lái),對(duì)于視覺(jué)稿上的元素尺寸換算,只需要原始的px值除以rem基準(zhǔn)值即可。例如此例視覺(jué)稿中的圖片,其尺寸是176px * 176px,轉(zhuǎn)換成為2.346667rem * 2.346667rem。
在實(shí)際生產(chǎn)當(dāng)中,如果每一次計(jì)算px轉(zhuǎn)換rem,或許會(huì)覺(jué)得非常麻煩,或許直接影響大家平時(shí)的開(kāi)發(fā)效率。為了能讓大家更快進(jìn)行轉(zhuǎn)換,我們團(tuán)隊(duì)內(nèi)的同學(xué)各施所長(zhǎng),為px轉(zhuǎn)換rem寫(xiě)了各式各樣的小工具。
CSSREM是一個(gè)CSS的px值轉(zhuǎn)rem值的Sublime Text3自動(dòng)完成插件。這個(gè)插件是由@正霖編寫(xiě)。先來(lái)看看插件的效果:

有關(guān)于CSSREM如何安裝、配置教程可以點(diǎn)擊這里查閱。
除了使用編輯器的插件之外,還可以使用CSS的處理器來(lái)幫助大家處理。比如說(shuō)Sass、LESS以及PostCSS這樣的處理器。我們簡(jiǎn)單來(lái)看兩個(gè)示例。
使用Sass的同學(xué),可以使用Sass的函數(shù)、混合宏這些功能來(lái)實(shí)現(xiàn):
|
1 2 3 4 5 6 7 8 9 |
@function px2em($px, $base-font-size: 16px) { <a href='http://www./members/jinyi7016'>@if</a> (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函數(shù)外,還可以使用Sass的混合宏:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@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 <a href='http://www./members/jinyi7016'>@if</a> $support-for-ie { #{$property}: $px-values; } //if there is only one (numeric) value, return the property/value line for it. <a href='http://www./members/jinyi7016'>@if</a> 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 <a href='http://www./members/jinyi7016'>@if</a> $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; } } |
有關(guān)于更多的介紹,可以點(diǎn)擊這里進(jìn)行了解。
除了Sass這樣的CSS處理器這外,我們團(tuán)隊(duì)的@頌奇同學(xué)還開(kāi)發(fā)了一款npm的工具px2rem。安裝好px2rem之后,可以在項(xiàng)目中直接使用。也可以使用PostCSS。使用PostCSS插件postcss-px2rem:
|
1 2 3 4 5 6 7 8 9 10 |
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中配置外,還可以使用其他的配置方式,詳細(xì)的介紹可以點(diǎn)擊這里進(jìn)行了解。
配置完成之后,在實(shí)際使用時(shí),你只要像下面這樣使用:
|
1 2 3 4 5 6 |
.selector { width: 150px; height: 64px; /*px*/ font-size: 28px; /*px*/ border: 1px solid #ddd; /*no*/ } |
px2rem處理之后將會(huì)變成:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
.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; } |
在整個(gè)開(kāi)發(fā)中有了這些工具之后,完全不用擔(dān)心px值轉(zhuǎn)rem值影響開(kāi)發(fā)效率。
rem前面大家都見(jiàn)證了如何使用rem來(lái)完成H5適配。那么文本又將如何處理適配。是不是也通過(guò)rem來(lái)做自動(dòng)適配。
顯然,我們?cè)趇Phone3G和iPhone4的Retina屏下面,希望看到的文本字號(hào)是相同的。也就是說(shuō),我們不希望文本在Retina屏幕下變小,另外,我們希望在大屏手機(jī)上看到更多文本,以及,現(xiàn)在絕大多數(shù)的字體文件都自帶一些點(diǎn)陣尺寸,通常是16px和24px,所以我們不希望出現(xiàn)13px和15px這樣的奇葩尺寸。
如此一來(lái),就決定了在制作H5的頁(yè)面中,rem并不適合用到段落文本上。所以在Flexible整個(gè)適配方案中,考慮文本還是使用px作為單位。只不過(guò)使用[data-dpr]屬性來(lái)區(qū)分不同dpr下的文本字號(hào)大小。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
.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; } |
為了能更好的利于開(kāi)發(fā),在實(shí)際開(kāi)發(fā)中,我們可以定制一個(gè)font-dpr()這樣的Sass混合宏:
|
1 2 3 4 5 6 7 8 9 10 11 |
@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; } } |
有了這樣的混合宏之后,在開(kāi)發(fā)中可以直接這樣使用:
|
1 |
<a href='http://www./members/sanjiaomaohero'>@include</a> font-dpr(16px); |
當(dāng)然這只是針對(duì)于描述性的文本,比如說(shuō)段落文本。但有的時(shí)候文本的字號(hào)也需要分場(chǎng)景的,比如在項(xiàng)目中有一個(gè)slogan,業(yè)務(wù)方希望這個(gè)slogan能根據(jù)不同的終端適配。針對(duì)這樣的場(chǎng)景,完全可以使用rem給slogan做計(jì)量單位。
本來(lái)想把這個(gè)頁(yè)面的用到的CSS(或SCSS)貼出來(lái),但考慮篇幅過(guò)長(zhǎng),而且這么簡(jiǎn)單的頁(yè)面,我想大家也能輕而易舉搞定。所以就省略了。權(quán)當(dāng)是給大家留的一個(gè)作業(yè)吧,感興趣的可以試試Flexible能否幫你快速完成H5頁(yè)面終端適配。
最后來(lái)看看真機(jī)上顯示的效果吧。我截了兩種設(shè)備下的效果:


其實(shí)H5適配的方案有很多種,網(wǎng)上有關(guān)于這方面的教程也非常的多。不管哪種方法,都有其自己的優(yōu)勢(shì)和劣勢(shì)。而本文主要介紹的是如何使用Flexible這樣的一庫(kù)來(lái)完成H5頁(yè)面的終端適配。為什么推薦使用Flexible庫(kù)來(lái)做H5頁(yè)面的終端設(shè)備適配呢?主要因?yàn)檫@個(gè)庫(kù)在手淘已經(jīng)使用了近一年,而且已達(dá)到了較為穩(wěn)定的狀態(tài)。除此之外,你不需要考慮如何對(duì)元素進(jìn)行折算,可以根據(jù)對(duì)應(yīng)的視覺(jué)稿,直接切入。
當(dāng)然,如果您有更好的H5頁(yè)面終端適配方案,歡迎在下面的評(píng)論中與我們一起分享。如果您在使用這個(gè)庫(kù)時(shí),碰到任何問(wèn)題,都可以在Github給我們提Issue。我們團(tuán)隊(duì)會(huì)努力解決相關(guān)需Issues。
|
|