「前端」rem縮放方案flexible-js兼容375px方案的思路

本文來自尚妝前端團隊南洋

發表于尚妝github博客,歡迎訂閱。

移動端H5頁面rem縮放方案flexible.js兼容375px方案的思路

參考:

移動端高清、多屏適配方案

viewport-and-flexible.js

flexible.js github

一個新的項目復用了一些老頁面,老頁面是使用375px方案進行移動端適配的,meta[viewport]使用的是<meta name="viewport" content="width=375, user-scalabe=no">,而新頁面使用的是flexible.js伸縮方案,動態生成meta[viewport]<meta name="viewport" content="initial-scale=[num], user-scalabe=no">
如何在老頁面使用px布局的前提下,新頁面使用rem布局,組件也使用rem布局,并且組件可以兼容老頁面和新頁面是本文的結果。
首先會介紹375px方案和rem方案的實現原理。

375px方案

<meta name="viewport" content="width=375, user-scalabe=no">
    375px方案的頁面開發過程對新人非常的友好,利用頁面的布局視口(layout viewport)為固定值375px,和移動端瀏覽器窗口的自動縮放功能(視覺視口==布局視口),可以很好的在大部分移動設備上展示375px寬度的內容。

    具體的開發前提是設計師給到一份750px寬的設計稿,前端同學根據ps量的像素的50%進行css書寫。若一個banner寬度量的為750px,在css中編寫為`width: 375px`。至于為什么是2倍的設計稿,可以參考[移動端高清、多屏適配方案](http://www.html-js.com/article/Mobile-terminal-H5-mobile-terminal-HD-multi-screen-adaptation-scheme%203041)這篇文章,可以找到答案。

    375px方案相對于把meta[viewport]中的width屬性設置成`device-width`,然后通過媒體查詢寫幾套css規則來說已經是非常方便了。把所有不同屏幕尺寸的手機的布局視口(layout viewport)設置成一個固定的值375px,無需考慮其他屏幕尺寸的情況。

    但375px方案的實現原理在某些安卓原生瀏覽器上有兼容問題,會產生一個重要bug --- 在某些安卓原生瀏覽器或webview中會出現視覺視口小于布局視口的情況。直觀的顯示就是頁面會出現左右滑動。如下圖:
375bug

而在上文也提到了375px方案得以實現就是依靠瀏覽器的原生能力 --- 迫使視覺視口等于布局視口。我們將這種情況下的document.documentElement.clientWidth(布局視口)window.innerWidth(視覺視口)打印看看。


瀏覽器的縮放效果沒有實現,至于為什么,先看兩條關于縮放的總結公式/經驗。

一、meta標簽內沒有設置initial-scale的情況

瀏覽器計算出的縮放值 = layout viewport width(布局視口) / ideal viewport width(理想視口)

visual viewport width(視覺視口) = 瀏覽器計算出的縮放值 * ideal viewport width (理想視口)
===》
layout viewport width === visual viewport width // true

經過上述計算會將視覺視口會等于布局視口,布局上的所有內容都會出現在手機屏幕上。出現之前提到的bug的問題出在計算視覺視口上,瀏覽器會將所有計算出的縮放值都默認等于1,所以不管我們將布局視口設置能375還是其他任意值,視覺視口永遠會是1 * ideal viewport width (理想視口)。ps:此款安卓機型的理想視口等于360.

二、meta標簽內設置了initial-scale的值的情況

visual viewport width(視覺視口) = initial-scale(meta 標簽內設置的初始縮放值) * ideal viewport width(理想視口又稱設備獨立像素)

layout viewport width = visual viewport width

解釋:第二條總結經驗正是rem伸縮方案flexiblejs的核心思想,設置了initial-scale后瀏覽器會計算出視覺視口,繼而將布局視口的值自動設置成視覺視口的值。達到在屏幕上完整呈現布局上的內容。

但是在同樣的安卓原生瀏覽器上,不管我們將initial-scale設置成多少,瀏覽器都默認值為1。所以視覺視口和布局視口永遠都等于1 * ideal viewport width這個問題的hack辦法在flexible.js里也有所體現。

375px方案就解釋到這里,至于為何是375而不是其他的值比如360、320等,可以參考移動端高清、多屏適配方案以及viewport-and-flexible.js, 在后篇文章中也有介紹3種視口的一些概念。

rem方案

rem方案的目標也是用一套相同的度量標準適配所有屏幕大小的移動設備,在不同屏下進行正確的縮放。假設10rem寬在iphone5上是屏幕寬的一半,那么10rem在iphone6、iphone6plus、三星note等等機型上都顯示為屏幕寬的一半。

我們知道rem所對應的px值是基于html標簽上的font-size值進行換算的。若

html {
  font-size: 20px;
}

10rem === 200px //true

為了適配縮放所有設備,就要寫個腳本動態設置html的fontSize值。同時要對頁面的布局視口(layout viewport)和設計稿做一個劃分約定,這里就約定這個值為10。(理論上可以任何值)

由以上兩幅圖可以知道,設計稿的一個區塊對應1rem,布局視口的一個區塊也對應1rem。而每個機型的布局視口該如何確定,flexible.js利用了上面提到的公式:

visual viewport width(視覺視口) = initial-scale(meta 標簽內設置的初始縮放值) * ideal viewport width(理想視口又稱設備獨立像素)

瀏覽器自動將 layout viewport width = visual viewport width

之前也提到了initial-scale不為1的情況下部分安卓機型有bug,所以這里可以將initial-scale規定設置成1。

拿iphone6為例:理想視口為375px,經計算布局視口和視覺視口都等于375,html的fontSize等于37.5px。若設計稿量的是10個區塊大小,那么在編寫css時就寫10rem,對應的width等于37。5 * 10 = 375px正好布滿整個布局視口。

在flexible.js中對iphone設備的initial-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; // initial-scale

此處將initial-scale根據dpr動態設置是為了解決retina屏下border: 1px問題。而將安卓設備的dpr全部設置成1就是hack了之前提到的initial-scale在部分安卓機子上只能為1的bug。

回到border: 1px問題,有些設計師在750px的設計稿中設計了一條1px的邊框線,而這個1px的邊框線在各機型上最好的效果實際是占設備物理像素的1px。

拿iphone5和初代iphone為例:若設置initial-scale=1,那么布局視口為320px,但是iphone5的物理像素寬為640px,那就代表了1個css像素包含了4個物理像素(2x2)。而初代iphone的布局視口和物理像素都為320px如圖:

在兩者手機上顯示的效果1px是一模一樣的,但是iphone5包含著4倍的物理像素,就取上下高度而言,在iphone5上只用編寫border: 0.5px就可以了。

但是部分機型對于0.5是不識別的,會直接賦值0。而我們想要做的就是令border的寬等于1個物理像素。我們可以這樣做,根據window.devicePixelRatio屬性動態縮放layout viewport,使之與屏幕的物理像素相同。這樣我們只用在css里編寫border: 1px,對應的就是物理像素的1px,border:1問題完美解決。

兩種方案如何兼容

首先要考慮到組件也用rem進行布局,并且組件要在px布局和rem布局中都能兼容。那么就要全局head引入flexible.js的js腳本。

在375px布局方案里meta[viewport]已經設置,那么在flexible腳本中就要進行判斷,已經設置viewport的就沿用,不動態創建meta[viewport]。

組件的rem布局要依賴html標簽的font-size值,在新頁面rem布局中已經實現。而在375px布局中,flexiblejs根據固定layout viewport值-- 375進行計算,那么所有屏幕尺寸下的頁面html標簽font-size值都為37.5px

還有一個問題,在375px布局中,全局css環境中設置了

html, body {
  font-size: 100%
}

而所有的瀏覽器實現默認字體大小為16px,所以在老頁面中有些字沒有設置大小,默認是16px,引入了flexible后html上的font-size為37.5px,body標簽上的字體大小就會變成37.5 * 100% = 37.5px,而沒有設置字體大小的字體就會變成37.5px,需要在flexible.js中設置針對這種情況。

if (doc.readyState === 'complete') {
        doc.body.style.fontSize = 16 + 'px';
    } else {
        doc.addEventListener('DOMContentLoaded', function(e) {
            doc.body.style.fontSize = 16 + 'px';
        }, false);
    }

最后的結果就是:

  • 全局引入flexible.js文件。
  • 375px布局的老頁面上html標簽的font-size固定為37.5px。body上的font-size固定為16px。
  • rem布局的新頁面上html標簽的font-size隨不同機型而不同。
  • 組件編寫一律按照rem布局,設計稿為750px,兼容新老頁面。

原創文章,轉載需謹慎 ~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在移動互聯網快速發展的今天,手機的種類和尺寸越來越多,作為前端的小伙伴們可能會越來越頭疼,但又不得不去適配一款又一...
    keenjaan閱讀 26,887評論 9 86
  • 原文地址 在移動設備上進行網頁的重構或開發,首先得搞明白的就是移動設備上的viewport了,只有明白了viewp...
    matthewm閱讀 1,563評論 0 4
  • 偶然間發現這篇文章,可以說,這是我見到的對移動rem單位做的最詳細,最細心的一篇總結性文章,果斷轉載至本博客,以便...
    阿寶er閱讀 1,165評論 5 51
  • 好漢,介于英雄和小人之間,林沖。 林沖的一生都是被害的,從客觀的角度來看林沖的一生都是被動的。梁山...
    Amy涵兒閱讀 320評論 0 1
  • 2017年1月21日 @NYC 感冒初愈,晚上去看了一場節目,雖是因為工作,但也意外獲得了一個靈感,讓人有點小興奮...
    Tess同學閱讀 168評論 0 0