使用 REM 進行佈局和適配.

雲水搖啊搖發表於2017-07-21

首先從螢幕開始說起.

螢幕是由一個一個顯示單元組成的.
1 每一個顯示單元都是物理世界真實存在的;
2 把一個顯示單元的大小稱為一個`物理畫素`;
3 通常我們所說的 `解析度`, 就是指一塊螢幕顯示單元的個數, 比如
750*1334, 表示這塊螢幕由 750*1334 個顯示單元組成

對映規則

畫素是計算機系統裡面的單位, 通常情況下, 我們讓一個畫素對應一個顯示單元. 所以有時候, 我們說螢幕高 667px, 實際上就是說, 螢幕的的高有 667個顯示單元的高度之和.

隨著技術的進步, 顯示單元可以做的越來越小, 比如以前是 10mm*10mm 的一個顯示單元, 現在我們可以做到 5mm*5mm 一個顯示單元.
為什麼追求顯示單元的小? 因為越小影像越精細.

但是: 顯示單元的變小, 意味著螢幕的解析度變大。

這裡就牽涉到了一些事情:

假設螢幕的大小不變, 但是解析度從 A, 變成 2A (也就是顯示單元縮小了一半)
並且: 一個畫素對應一個顯示單元, 這個規則始終不變

此時, 你原來寬度為 100px 的一個元素, 在這個 2A 螢幕上渲染出來, 你會明顯的發現:
在視覺上: 這個 100px 明顯比之前小了, 和之前的 50px 的時候一樣大小.

那怎麼辦啊, 這樣顯示肯定是不可以的, 所以我們要對這個情況做處理:

1 我們規定, 大小為 n*n 的顯示單元, 是標準的顯示單元, 標準意味著它合乎我們長久的判斷: 100px 在物理世界大概有多大.

2 我們要知道當前螢幕的顯示單元, 和標準顯示單元之間的大小比例,比如說當前螢幕的顯示單元的大小是標準的一半還是 三分之一.

通過 devicePixelRatio 屬性來獲

我們可以認為:
devicePixelRatio 標記是: 標準顯示單元/當前裝置的顯示單元

建立在上面的基礎上面, 你就可以動態的調整元素的大小, 比如說某個元素 x 的寬度是 100px;

在 devicePixelRatio = 1 的裝置上面寬度是 100px
在 devicePixelRatio = 2 的裝置上面寬度就要是 200px;

ok, 那麼我們來搞.
根據不同的 devicePixelRatio 來調整元素的樣式.

var box = document.querySelector(`.box`);

var height = parseInt(getComputedStyle(box).height);
var width = parseInt(getComputedStyle(box).width);

box.style.height = height * parseInt(window.devicePixelRatio) + `px`;
box.style.width = width * parseInt(window.devicePixelRatio) + `px`;

這僅僅是一個元素的兩個屬性, 1000個元素, 每個元素 5 個屬性, 就可以讓你哭掉了.
所以這種處理方式肯定是不可以的.

然後我們發現了 rem 單位.
它的簡單解釋:

當你給某個元素A 設定了 height:2rem 的時候
它會找到根節點(html) 的 font-size 值, 比如是 16px
然後拿 16 * 2 = 32px
作為元素A 的最終 height.

這個就可以利用了
1 讓元素使用 rem 作單位
2 然後控制根元素的 font-size 值, 在不同的 devicePixelRatio 下面的時候, 呈現不同的值
比如你可以設定:

devicePixelRatio = 1, font-size(root) = 100px;
devicePixelRatio = 2, font-size(root) = 200px;

元素在這個時候, 就會自動響應大小的變化.

好, 開始搞:

var fontSize = 100 * parseInt(window.devicePixelRatio) + `px`;
document.documentElement.style.fontSize = fontSize;

嗯, 結果還是不錯的, 在不同的解析度下面, 我們也能實現頁面相同了.

實際上, 上面的討論, 已經解決了我們的問題:
在相同物理尺寸下的裝置, 如何在解析度不同的情況下, 讓一個 100px 的元素, 它對應的物理世界的
大小, 始終相同?

現在更近一步, 上面的討論, 固定了一個變數: 螢幕尺寸, 現在放開這個變數, 固定螢幕的解析度這個變數.
這個問題就變成適配問題了:

場景描述:
比如你的一個頁面本來是以 375寬度為基礎做出來的, 那麼在裝置的寬度變成 320px 的時候,
你的頁面就會出現問題: 擠壓, 變形, 錯亂, 或者超出隱藏, 超出滾動等等操作.

怎麼辦啊?
希望的是在 320 也能正常顯示: 讓頁面上的所有元素都縮小一些, 也就ok了. 比如一個元素
在 375 裝置上面顯示這麼大, 在 320 上面顯示成這麼大不就行了.

那麼如何縮小?
rem;
你想下, 只要在螢幕的寬度變小的時候, 讓根元素的 font-size 跟著變小, 那麼所有使用 rem 作為單位的
元素, 是不是也跟著變小, 目標就達成了.

那麼怎麼讓 font-size(root) 隨著螢幕的寬度變小而變小啊.

  1. 選一對基準值, 比如: 375px/100px; 表示螢幕寬度為 375的時候, font-size(root) 為 100;

  2. 每次計算一下就好, 比如發現螢幕的當前寬度為 320, 那麼算不出來此時的 font-size(root) 嗎??
    算出來不會設定根元素的 font-size 嗎?

好吧, 上面說的暫時都不要試, 先提一個事情.
所有的上面的討論, 實際上都建立在:

當你螢幕的解析度是 100100 的時候, 你就擁有一份 100100 大小的容器, 用來呈現你的網頁.
比如說, 你的 iPhone7 的解析度是 6671334, 那麼你就擁有一份 6671334 大小的容器來放你的網頁

可惜並不是這樣的.

從 iPhone 釋出前夕說起:

開發人員發現, 原本為 pc 開發的網頁
在 iPhone 上面顯示不全, 這部分可以通過滾動條來解決.
但是使用 百分比佈局的頁面就坑爹了, 原本在 pc 端瀏覽器上擁有
的 20% 在 iPhone 上面就一點點了, 佈局完全亂了, 坑啊.
為了解決這個問題, 開發人員提出了一個的新的玩意: `layout viewport`

我該怎麼解釋這個玩意呢.

    ==============  // 這個是你的百分比頁面所基於的寬度

    ===             // 這個是你螢幕的寬度

這樣一來, 頁面肯定會錯亂. 所以提出的 layout viewport 把模型變成這樣:

    ==============  // 這個是你的百分比頁面所基於的寬度

    =============   // layout viewport 的寬度

    ===             // 這個是你螢幕的寬度

你的頁面會被放到 layout viewport 這個容器上面, 然後再將 layout viewport 縮小到
和螢幕寬度一樣的大小.
並且允許使用者放大頁面,通過滾動條滑動來瀏覽器全部頁面.

在最初的時候, 這種方式的確解決了 pc 端頁面在手機上瀏覽的問題, 但是隨著移動端的興起,
大量的針對移動端的頁面被製作出來, 也就是模型變成這樣:

    ===   // 針對移動端做的頁面

    ============   // layout viewport 

    ===   // 螢幕的寬度

這樣很明顯就出現問題了: 你的頁面先放到 layout viewport 上面, 然後又縮小到和螢幕寬度一致
最終顯示出來的, 就是你的頁面明顯被縮小了.

所以我們要解決這個問題, 要把 layout viewport 的大小, 變成和螢幕的寬度一致.
這裡假設螢幕的顯示單元始終是標準的顯示單元大小。

怎麼讓 layout viewport 變成和螢幕的寬度一致呢?
通過 meta name=”viewport” 標籤.

解釋一下:
meta name=”viewport” 有一個 content 屬性, 它裡面有幾個值, 可以用來對 layout viewport
做處理. content 有如下幾個欄位:

initial-scale: 這個值會影響最終 layout viewport 的寬度, 計算公式應該是這樣:


螢幕的解析度/(devicePixelRatio*initial-scale) = 最終的 layout viewport 的寬度. 

螢幕的解析度我們可以拿到, devicePixelRatio 也可以拿到.
比如 iPhone7, 螢幕解析度是 750*1334, devicePixelRatio=2, 當你設定 initial-scale=1 的時候
layout viewport 的最終寬度就是 375;

這裡有一個點, 我說一下;
我們可以讓 layout viewport 的寬度是任意值, 通過對 initial-scale 的設定.
那我們要設定它為多少呢?

可以設定成 375, 這個寬度, 是以標準顯示單元為單位算出來的寬度
也可以設定成 750, 這樣的話, 你的 1px 就完整對應這個裝置的 1 個顯示單元.

我們選擇後者, 因為這個牽涉到 1px border 的實現.
如果設定成這個, 那麼你的 initial-scale 始終只要設定成 1/devicePixelRatio 即可,
因為 devicePixelRatio * 1/devicePixelRatio = 1;

還有其他的兩個相關屬性:
maximum-scale: 最大能放大多少
minimum-scale: 最小能放大多少

希望不能縮放, 因為我們的頁面不需要縮放就能正常顯示, 縮放了反而顯示不正確。

最終統籌一下:
我們要做的事情
1 讓 layout viewport 變成和螢幕解析度一致的寬度
2 根據裝置寬度和 devicePixelRatio 來指明根元素的 font-size 值

這些操作之後, 你就可以實現最終的程式碼了.

相關文章