手機淘寶的flexible設計與實現

一喵嗚發表於2015-07-14

看到小黑的文章 關於webapp中的文字單位的一些搗騰 感覺很贊。尤其是,他提到了手機淘寶的meta,所以覺得要講講我們這方面的一些實踐。

手機淘寶從2014年中開始,全面推行flexible設計。什麼叫flexible呢?其實flexible就是responsive的低端形態和基礎。對我們來說,最直觀的感受就是,在超寬螢幕上,網頁顯示不會兩邊留白。以前pc時代大家經常講的流體佈局,其實就是一種flexible design。只不過,流體的表述角度是實現,flexible的表述角度是結果,為了跟高大上的responsive保持一致,我們這裡使用了flexible這個說法。

討論方案之前,需要先了解三個關鍵概念:

  • 單位英寸畫素數(Pixel Per Inch,PPI):現實世界的一英寸內畫素數,決定了螢幕的顯示質量
  • 裝置畫素比率(Device Pixel Ratio,DPR):物理畫素與邏輯畫素(px)的對應關係
  • 解析度(Resolution):螢幕區域的寬高所佔畫素數

當我們決定不同螢幕的字型和尺寸的單位時,螢幕的這幾個引數非常重要。

場景1——Resolution適配

一張banner圖片,當你面對不同的螢幕時你希望它的行為是怎樣的?

在這個場景中,我們主要需要面對的是解析度適配問題,考慮到多數網頁都是縱向滾動的,在不同的螢幕尺寸下,banner的行為應該是總是鋪滿螢幕寬度以及總是保持寬高比

最自然的思路是使用百分比寬度,但是假如使用百分比寬度,即width:100%,我們又有兩種思路來實現固定寬高比:一是利用img標籤的特性,只設寬度等圖片載入完,這種方法會導致大量的重排,並且非固定高度會導致懶載入等功能難以實現,所以果斷放棄;二是使用before偽元素的margin撐開高度,這種方法是比較乾淨的純css實現,但是不具備任何複用性而且要求特定html結構,所以也只好放棄了。

於是,剩下最合適的辦法是使用其它相對單位,本來最合適的單位是vw,它的含義是視口寬度,但是這個單位存在嚴重的相容問題,所以也只好放棄。

最後我們只好配合js來做,硬算也是一條路,但是同樣不具備任何可複用性,最終我們選擇了rem,我們用js給html設定一個跟螢幕寬度成正比的font-size,然後把元素寬高都用rem作為單位。

這是我們目前的線上方案了,它是一個近乎Hack的用法,已知的問題包括:

  • 某些Android機型會丟掉rem小數部分
  • 佔用了rem單位
  • 不是純css方案

場景2——PPI適配

一段文字,當你面對不同的螢幕時你希望它的行為是怎樣的?

顯然,我們在iPhone3G和iPhone4的Retina屏下面,希望看到的文字尺寸是相同的,也就是說,我們不希望文字在Retina屏尺寸變小,此外,我們在大屏手機上,希望看到更多文字,以及,現在絕大多數的字型檔案,是自帶一些點陣尺寸的,通常是16px和24px,所以我們不希望出現13px、15px這樣的奇葩尺寸

這樣的特徵決定了,場景1中的rem方案,不適合用到段落文字上。所以段落文字應該使用px作為單位,考慮到Retina,我們利用media query來指定不同的字型,考慮到dpr判定的相容性,我們用寬度替換來代替:

.a {
    font-size:12px
}
@media (min-width: 401px){
    .a {
        font-size:24px
    }
}

另一種場景,一些標題性文字,希望隨著螢幕寬而增大的,我們可以仍然使用rem作為單位。超過35px(個人直觀感受)的文字,已經不用太考慮點陣資訊了,靠字型的向量資訊也能渲染的很好。

場景3——DPR匹配

一個區塊,設計稿上有1畫素邊框,當你面對不同的螢幕時你希望它的行為是怎樣的?

這個場景,需求很簡單,設計師希望在任何螢幕上這條線都是1物理畫素

好吧,當然這個問題的答案不是寫1px那麼簡單。在retina屏下面,如果你寫了這樣的meta

<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

你將永遠無法寫出1px寬度的東西,除此之外,inline的SVG等元素,也會按照邏輯畫素來渲染,整個頁面的清晰度會打折。

所以,手機淘寶用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);
}

結語

總的來說,手機淘寶的flexible方案是綜合運用rem和px兩種單位+js設定scale和html字型。

這些JS的內容,可以在我們開源的庫ml中找到:

https://github.com/amfe/lib.flexible

相關文章