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

參考:css

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

viewport-and-flexible.js前端

flexible.js githubandroid

一個新的項目複用了一些老頁面,老頁面是使用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方案的實現原理。git

375px方案

<meta name="viewport" content="width=375, user-scalabe=no">

        375px方案的頁面開發過程對新人很是的友好,利用頁面的佈局視口(layout viewport)爲固定值375px,和移動端瀏覽器窗口的自動縮放功能(視覺視口==佈局視口),能夠很好的在大部分移動設備上展現375px寬度的內容。github

        具體的開發前提是設計師給到一份750px寬的設計稿,前端同窗根據ps量的像素的50%進行css書寫。若一個banner寬度量的爲750px,在css中編寫爲width: 375px。至於爲何是2倍的設計稿,能夠參考移動端高清、多屏適配方案這篇文章,能夠找到答案。web

        375px方案相對於把meta[viewport]中的width屬性設置成device-width,而後經過媒體查詢寫幾套css規則來講已是很是方便了。把全部不一樣屏幕尺寸的手機的佈局視口(layout viewport)設置成一個固定的值375px,無需考慮其餘屏幕尺寸的狀況。瀏覽器

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

375bug

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


瀏覽器的縮放效果沒有實現,至於爲何,先看兩條關於縮放的總結公式/經驗。

1、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.

2、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在iphone六、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,兼容新老頁面。

對我以上的理解有疑問和意見的歡迎找我私聊~微博-寫前端的暹羅

相關文章
相關標籤/搜索