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

尚妝產品技術刊讀發表於2017-03-02

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

發表於尚妝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倍的設計稿,可以參考移動端高清、多屏適配方案這篇文章,可以找到答案。

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

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

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

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

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

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

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

一、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。(理論上可以任何值)

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

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

由以上兩幅圖可以知道,設計稿的一個區塊對應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如圖:

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

在兩者手機上顯示的效果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,相容新老頁面。

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


本文對你有幫助?歡迎掃碼加入前端學習小組微信群:

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

相關文章