在閱讀本文之前,先需要了解 設(shè)備像素眶熬、設(shè)備獨(dú)立像素妹笆、CSS-像素块请、桌面瀏覽器上的-viewport、移動瀏覽器上的-viewport拳缠。
rem 是 css 的長度單位墩新,它是相對于 <html>
元素的 font-size
的相對值。假設(shè) html { font-size: 20px; }
窟坐,那么 1rem 就等于 20px海渊。
根據(jù) layout viewport 來設(shè)置 rem
可以根據(jù) layout viewport 的大小,來設(shè)置<html>
元素的 font-size
的值哲鸳。
function () {
const setRem = () => document.documentElement.style.fontSize = document.documentElement.clientWidth / 10
window.addEventListener('DOMContentLoaded', setRem, false)
window.addEventListener('orientationchange', setRem, false)
} ()
上面的腳本根據(jù)手機(jī)瀏覽器的 layout viewport 來設(shè)置 <html>
元素的 font-size
臣疑。 因此在任何手機(jī)上,1rem 都是 1/10 layout viewport徙菠。由于我們一般都會加上 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
標(biāo)簽讯沈,所以 layout viewport 和 visual viewport 是相等的,都代表手機(jī)屏幕能夠顯示的 css 像素婿奔,因此在任何手機(jī)上缺狠,1rem 都是 1/10 手機(jī)屏幕的寬度,10rem 都是占滿手機(jī)屏幕的寬度萍摊。
當(dāng)網(wǎng)頁需要在移動端顯示的時候挤茄,我們給元素設(shè)置 css 尺寸的時候,都要使用 rem冰木,不要使用 px穷劈,因?yàn)槭褂?px 的元素在不同的手機(jī)上顯示效果差別太大。舉個例子片酝,某個元素的 width 為 320px囚衔,那么該元素在 iphone5 上滿屏顯示,在 iphone6 上則不會滿屏顯示雕沿,右側(cè)會留有 55px 的空白练湿。如果某個元素的 width 為 10rem,則無論在 iphone5 還是 iphone6 上审轮,都是滿屏顯示肥哎。
如果給我們一個適配 iphone6 的網(wǎng)頁設(shè)計稿,該設(shè)計稿的尺寸應(yīng)該為 750 物理像素疾渣,因?yàn)?iphone6 的設(shè)備物理分辨率為 750篡诽。iphone6 的 devicePixelRatio (以下簡稱 dpr) 為 2,也就是用 2 個物理像素來顯示 1 個設(shè)備獨(dú)立像素榴捡。在不縮放網(wǎng)頁的情況下杈女,1 個 css 像素由 1 個設(shè)備獨(dú)立像素來渲染,也就是由 2 個物理像素渲染。因此 750 物理像素轉(zhuǎn)換為 css 像素應(yīng)該是 375px达椰。如果我們在適配 iphone6 的設(shè)計稿上測量某個元素的尺寸為 64 物理像素翰蠢,那么該元素的 css 像素應(yīng)該是 32px。根據(jù)上面腳本的設(shè)置啰劲,iphone6 上 1rem 相當(dāng)于 37.5px梁沧,所以 32px 就相當(dāng)于 32 / 37.5
rem。為了避免每次計算元素的 rem 尺寸都需要用 css 像素除以 37.5蝇裤,我們可以寫成 sass 的函數(shù) (該 sass 函數(shù)只適用于設(shè)計稿是 iphone6 的情況)廷支。
@function px2rem($px) {
@return $px / 37.5 + rem;
}
假設(shè)我們的網(wǎng)頁設(shè)計稿的尺寸為 750 像素,那么該設(shè)計稿就是針對 iphone6 的設(shè)計稿栓辜。在設(shè)計稿上測量某個元素為 100 像素恋拍,則只需要指定 width: px2rem(50)
。之所以參數(shù)是 50 不是 100藕甩,是因?yàn)?100 是物理像素芝囤,iphone6 上 100 物理像素就相當(dāng)于 50px。如果覺得麻煩辛萍,可以將 iphone6 的設(shè)計稿縮放成 375 像素,這樣再在設(shè)計稿上測量出某個元素為 50 像素羡藐,就是 50px贩毕,不需要除以 2 了。
根據(jù) devicePixelRatio 來設(shè)置 rem
根據(jù) layout viewport 的大小來設(shè)置 rem仆嗦,可以保證只要使用 rem 設(shè)置元素的尺寸辉阶,同一個元素在不同的手機(jī)上所占屏幕的百分比都是相同的,這就相當(dāng)于元素采用百分比來布局瘩扼。在大屏幕的手機(jī)上谆甜,該元素所占的物理尺寸就大,在小屏幕的手機(jī)上集绰,該元素所占的物理尺寸就小规辱。但是有些情況下,我們希望某個元素在不同手機(jī)上栽燕,所占的物理尺寸都差不多罕袋,而不考慮該元素所占屏幕的百分比是多少 (比如我們設(shè)置字體大小,希望字體在不同設(shè)備上的物理尺寸都差不多)碍岔。這種情況下浴讯,根據(jù) layout viewport 來設(shè)置 rem 就不適合了。
我們可以采用一種新的方案來代替 根據(jù) layout viewport 來設(shè)置 rem 的方案蔼啦,那就是 根據(jù)設(shè)備的 devicePixelRatio 來設(shè)置 rem 1rem = 50px * dpr
榆纽。dpr 為 1 的設(shè)備上,1rem = 50px,dpr 為 2 的設(shè)備上奈籽,1rem = 100px饥侵。同時還會根據(jù) devicePixelRatio 設(shè)置 <meta name="viewport">
標(biāo)簽的 initial-scale 的值 initial-scale = 1 / dpr
。dpr 為 1 的設(shè)備上唠摹,initial-scale = 1爆捞,dpr 為 2 的設(shè)備上,initial-scale = 0.5勾拉。這樣可以保證煮甥,在不同的設(shè)備上,相同 rem 的元素所占的物理尺寸差不多藕赞。
我們通過在 <head>
標(biāo)簽內(nèi)加上下面的壓縮的 js 代碼成肘,可以實(shí)現(xiàn)根據(jù) devicePixelRatio 來設(shè)置 rem。另外我們不要自己定義 <meta name="viewport">
標(biāo)簽斧蜕,因?yàn)橄旅娴拇a會自動定義該標(biāo)簽双霍,并且根據(jù) devicePixelRatio 來設(shè)置 initial-scale 的屬性值。
<script>!function(e){function t(a){if(i[a])return i[a].exports;var n=i[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=window;t["default"]=i.flex=function(normal,e,t){var a=e||100,n=t||1,r=i.document,o=navigator.userAgent,d=o.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i),l=o.match(/U3\/((\d+|\.){5,})/i),c=l&&parseInt(l[1].split(".").join(""),10)>=80,p=navigator.appVersion.match(/(iphone|ipad|ipod)/gi),s=i.devicePixelRatio||1;p||d&&d[1]>534||c||(s=1);var u=normal?1:1/s,m=r.querySelector('meta[name="viewport"]');m||(m=r.createElement("meta"),m.setAttribute("name","viewport"),r.head.appendChild(m)),m.setAttribute("content","width=device-width,user-scalable=no,initial-scale="+u+",maximum-scale="+u+",minimum-scale="+u),r.documentElement.style.fontSize=normal?"50px": a/2*s*n+"px"},e.exports=t["default"]}]); flex(false,100, 1);</script>
下面是這段代碼的源碼批销。
'use strict';
/**
* @param {Boolean} [normal = false] - 默認(rèn)開啟頁面壓縮以使頁面高清;
* @param {Number} [baseFontSize = 100] - 基礎(chǔ)fontSize, 默認(rèn)100px;
* @param {Number} [fontscale = 1] - 有的業(yè)務(wù)希望能放大一定比例的字體;
*/
const win = window;
export default win.flex = (normal, baseFontSize, fontscale) => {
const _baseFontSize = baseFontSize || 100;
const _fontscale = fontscale || 1;
const doc = win.document;
const ua = navigator.userAgent;
const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);
const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;
const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
let dpr = win.devicePixelRatio || 1;
if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {
// 如果非iOS, 非Android4.3以上, 非UC內(nèi)核, 就不執(zhí)行高清, dpr設(shè)為1;
dpr = 1;
}
const scale = normal ? 1 : 1 / dpr;
let metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
doc.head.appendChild(metaEl);
}
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);
doc.documentElement.style.fontSize = normal ? '50px' : `${_baseFontSize / 2 * dpr * _fontscale}px`;
};
根據(jù) devicePixelRatio 來設(shè)置 rem洒闸,在 iphone6 上,dpr = 2均芽,1rem = 100px丘逸,initial-scale=0.5,layout viewport = 750px掀宋,因此如果在適配 iphone6 的 750 像素的設(shè)計稿上測量一個元素的尺寸為 100 像素深纲,則可以直接轉(zhuǎn)換為 1rem,測量某個元素為 12px劲妙,則轉(zhuǎn)換為 0.12rem湃鹊,這樣我們可以很方便的將測量的像素轉(zhuǎn)換為 rem。
總結(jié)
盡量使用根據(jù) devicePixelRatio 來設(shè)置 rem镣奋,對于那些固定大小的元素 (字體币呵、底部導(dǎo)航欄圖標(biāo)等),設(shè)置 rem 尺寸侨颈,對于可變大小的元素 (布局元素)富雅,使用百分比尺寸,或者使用 flex 布局肛搬。