在 web 的世界裡,無線和 PC 的響應式適配其實是兩個世界……
1. 視口 viewport
1.1 viewport 基礎
viewport 解釋為中文就是‘視口’的意思,也就是瀏覽器中用於顯示網頁的區域。在 PC 端,其大小也就是瀏覽器可視區域的大小,所以我們也不會太關注此概念;而在移動端,絕大多數情況下 viewport 都大於瀏覽器可視區,保證 PC 頁面在移動瀏覽器上面的可視性。為提升可視性體驗,針對移動端有了對 viewport 的深入研究。
1.2 viewport 詳解
在移動端有三種型別的 viewport: layoutviewport、visualviewport、idealviewport。具體解釋如下:
- layoutviewport: 大於實際螢幕, 元素的寬度繼承於 layoutviewport,用於保證網站的外觀特性與桌面瀏覽器一樣。layoutviewport 到底多寬,每個瀏覽器不同。iPhone 的 safari 為 980px,通過 document.documentElement.clientWidth 獲取。
- visualviewport: 當前顯示在螢幕上的頁面,即瀏覽器可視區域的寬度。
- idealviewport: 為瀏覽器定義的可完美適配移動端的理想 viewport,固定不變,可以認為是裝置視口寬度。比如 iphone 7 為 375px, iphone 7p 為 414px。
1.3 viewport 設定
我們通過對幾種 viewport 設定可以對網頁的展示進行有效的控制,在移動端我們經常會在 head 標籤中看到這段程式碼:
<meta name='viewport' content='width=device-width,initial-scale=1,user-scale=no' />
複製程式碼
通過對 meta 標籤三個 viewport 的設定,最終使頁面完美展示。下面詳細的闡釋其具體含義:
- width 設定的是 layoutviewport 的寬度
- initial-scale 設定頁面的初始縮放值,並且這個初始縮放值是相對於 idealviewport 縮放的,最終得到的結果不僅會決定 visualviewport,還會影響到 layoutviewport
- user-scalable 是否允許使用者進行縮放的設定
對上面的說明通過公式推導進行進一步的解釋:
// 設定兩個變數:
viewport_1 = width;
viewport_2 = idealviewport / initial-scale;
// 則:
layoutviewport = max{viewport_1, viewport_2};
visualviewport = viewport_2;
複製程式碼
只要 layoutviewport === visualviewport,頁面下面不會出現滾動條,預設只是把頁面放大或縮小。
1.4 viewport 舉例
以下是通過改變 meta viewport 的幾個引數的值來算取不同的 viewport:
width | initial-scale | layoutviewport | visualviewport | idealviewport | 是否滾動 |
---|---|---|---|---|---|
- | - | 980px | 980px | 375px | 否 |
device-width | 1 | 375px | 375px | 375px | 否 |
device-width | 2 | 375px | 188px | 375px | 是 |
device-width | 0.5 | 750px | 750px | 375px | 否 |
480px | 1 | 480px | 375px | 375px | 是 |
480px | 2 | 480px | 188px | 375px | 是 |
480px | 0.5 | 750px | 750px | 375px | 否 |
以上是針對 iphone 6/7/8 的測試資料,且無論怎麼設定 viewport 都具有臨界值,即:75 <= layoutviewport <= 10000,75 <= visualviewport <= 1500。
1.5 為什麼要設定 viewport
viewport 的設定不會對 PC 頁面產生影響,但對於移動頁面卻很重要。下面我們舉例來說明:
- 媒體查詢 @media 響應式佈局中,會根據媒體查詢功能來適配多端佈局,必須對 viewport 進行設定,否則根據查詢到的尺寸無法正確匹配視覺寬度而導致佈局混亂。如不設定 viewport 引數,多說移動端媒體查詢的結果將是 980px 這個節點佈局的引數,而非我們通常設定的 768px 範圍內的這個佈局引數
- 由於目前多數手機的 dpr 都不再是 1,為了產出高保真頁面,我們一般會給出 750px 的設計稿,那麼就需要通過設定 viewport 的引數來進行整體換算,而不是在每次設定尺寸時進行長度的換算。
2. 裝置畫素比 dpr 與 1px 物理畫素
2.1 物理畫素(physical pixel)
手機螢幕上顯示的最小單元,該最小單元具有顏色及亮度的屬性可供設定,iphone6、7、8 為:750 * 1334,iphone6+、7+、8+ 為 1242 * 2208
2.2 裝置獨立畫素(density-indenpendent pixel)
此為邏輯畫素,計算機裝置中的一個點,css 中設定的畫素指的就是該畫素。老早在沒有 retina 屏之前,裝置獨立畫素與物理畫素是相等的。
2.3 裝置畫素比(device pixel ratio)
裝置畫素比(dpr) = 物理畫素/裝置獨立畫素。如 iphone 6、7、8 的 dpr 為 2,那麼一個裝置獨立畫素便為 4 個物理畫素,因此在 css 上設定的 1px 在其螢幕上佔據的是 2個物理畫素,0.5px 對應的才是其所能展示的最小單位。這就是 1px 在 retina 屏上變粗的原因,目前有很多辦法來解決這一問題。
2.4 1px的物理畫素的解決方案
從第一部分的討論可知 viewport 的 initial-scale 具有縮放頁面的效果。對於 dpr=2 的螢幕,1px壓縮一半便可與1px的裝置畫素比匹配,這就可以通過將縮放比 initial-scale 設定為 0.5=1/2 而實現。以此類推 dpr=3的螢幕可以將 initial-scale設定為 0.33=1/3 來實現。
3. 裝置畫素比 dpr 與 rem 的適配方案
結合 2、3 部分可以實現 1px 的物理畫素這一最小螢幕單位,那在此基礎上如可讓設計通常提供的 750px 設計稿來完美的適配到多種機型上,使用 rem 是一種解決方式。
3.1 rem 如何設定
rem 是相對於根元素 html 的 font-size 來做計算。通常在頁面初始化時載入時通過對document.documentElement.style.fontSize
設定來實現。
3.2 rem 適配規則
通過對 initial-scale = 1/dpr 的設定,已將對螢幕的描述從物理畫素轉化到了物理畫素上了,這將是後續推導的基礎,且設計稿為 750px。
- 物理畫素為 750 = 375 * 2,若螢幕等分為 10 份,那麼 1rem = 75px,10rem = 750px;
- 物理畫素為 1125 = 375 * 3,若螢幕等分為 10 份,那麼 1rem = 112.5px, 10rem = 1125px;
- 物理畫素為 1242 = 414 * 3, 若螢幕等分為 10 份,那麼 1rem = 124.2px, 10rem = 1242px;
因此可推匯出 rem 的設定方式:
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
複製程式碼
下面我們將 750px 下,1rem 代表的畫素值用 baseFont 表示,則在 baseFont = 75 的情況下,是分成 10 等份的。因此可以將上面的公式通用話一些:
document.documentElement.style.fontSize = document.documentElement.clientWidth / ( 750 / 75 ) + 'px';
複製程式碼
整體設定可參考如下程式碼:
(function (baseFontSize) {
const _baseFontSize = baseFontSize || 75;
const ua = navigator.userAgent;
const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
const dpr = window.devicePixelRatio || 1;
if (!isIos && !(matches && matches[1] > 534)) {
// 如果非iOS, 非Android4.3以上, dpr設為1;
dpr = 1;
}
const scale = 1 / dpr;
const metaEl = document.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = document.createElement('meta');
metaEl.setAttribute('name', 'viewport');
window.document.head.appendChild(metaEl);
}
metaEl.setAttribute('content', 'width=device-width,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale);
document.documentElement.style.fontSize = document.documentElement.clientWidth / (750 / _baseFontSize) + 'px';
})();
複製程式碼
同時為了書寫方便可以直接通過 px 佈局,然後在打包時利用 pxtorem 庫轉化為基於 rem 的佈局。
4. 視口單位適配方案
將視口寬度 window.innerWidth
和視口高度 window.innerHeight
等分為 100 份,且將這裡的視口理解成 idealviewport 更為貼切,並不會隨著 viewport 的不同設定而改變。
- vw : 1vw 為視口寬度的 1%
- vh : 1vh 為視口高度的 1%
- vmin : vw 和 vh 中的較小值
- vmax : 選取 vw 和 vh 中的較大值
如果設計稿為 750px,那麼 1vw = 7.5px,100vw = 750px。其實設計稿按照設麼都沒多大關係,最終轉化過來的都是相對單位,上面講的 rem 也是對它的模擬。這裡的比例關係也推薦不要自己換算,使用 pxtoviewport 的庫就可以幫我們轉換。當然每種方案都會有其弊端,這裡就不展開討論。
總結
在移動端開發中,理解視口對適配至關重要。因此本文先從視口展開討論,從而引出 1px、rem 及 vw/vh 這些和適配相關的主要話題。下面提供的參考文章會在某些點上更加細化,以供參考。