簡(jiǎn)單-了解真實(shí)的『REM』手機(jī)屏幕適配

rem 作為一個(gè)低調(diào)的長(zhǎng)度單位吁系,由于手機(jī)端網(wǎng)頁的興起茸炒,在屏幕適配中得到重用。使用 rem 前端開發(fā)者可以很方便的在各種屏幕尺寸下碌补,通過等比縮放的方式達(dá)到設(shè)計(jì)圖要求的效果动猬。

rem 的官方定義『The font size of the root element.』啤斗,即以根節(jié)點(diǎn)的字體大小作為基準(zhǔn)值進(jìn)行長(zhǎng)度計(jì)算。一般認(rèn)為網(wǎng)頁中的根節(jié)點(diǎn)是 html 元素赁咙,所以采用的方式也是通過設(shè)置 html 元素的 font-size 來做屏幕適配钮莲,但實(shí)際情況真有這么簡(jiǎn)單嗎?

首先我們來看看使用 rem 實(shí)現(xiàn)手機(jī)屏幕適配的常用方案彼水。

以設(shè)計(jì)稿的寬度為640px崔拥,即:designWidth = 640,同時(shí)設(shè)定在640px屏寬下 1rem=100px 凤覆,即:rem2px = 100链瓦。

設(shè)置 1rem=100px 的優(yōu)點(diǎn)不言而喻。前端開發(fā)者在切圖盯桦、重構(gòu)頁面的時(shí)候慈俯,通過直接位移小數(shù)點(diǎn)的方式渤刃,就可以將UI圖中測(cè)量到的 px 值換算成對(duì)應(yīng)的 rem 值,方便快捷肥卡。

此外溪掀,在 head 中我們還設(shè)置了:<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />
viewport 的作用很重要,但不是本文的重點(diǎn)所以不展開步鉴,有興趣的同學(xué)可以自行搜索揪胃。

先來看看具體方案:

下面四個(gè)方案來自同事共享,原理都是采用等比縮放的方式 —— 獲得目標(biāo)屏幕寬度和設(shè)計(jì)稿寬度的比氛琢,作為 rem 的基值(縮放系數(shù))喊递,設(shè)置為html標(biāo)簽的字體大小。不同的只是在于性能取舍和書寫習(xí)慣阳似。

方案1

@media screen and (min-width: 320px) {html{font-size:50px;}}
@media screen and (min-width: 360px) {html{font-size:56.25px;}}
@media screen and (min-width: 375px) {html{font-size:58.59375px;}}
@media screen and (min-width: 400px) {html{font-size:62.5px;}}
@media screen and (min-width: 414px) {html{font-size:64.6875px;}}
@media screen and (min-width: 440px) {html{font-size:68.75px;}}
@media screen and (min-width: 480px) {html{font-size:75px;}}
@media screen and (min-width: 520px) {html{font-size:81.25px;}}
@media screen and (min-width: 560px) {html{font-size:87.5px;}}
@media screen and (min-width: 600px) {html{font-size:93.75px;}}
@media screen and (min-width: 640px) {html{font-size:100px;}}
@media screen and (min-width: 680px) {html{font-size:106.25px;}}
@media screen and (min-width: 720px) {html{font-size:112.5px;}}
@media screen and (min-width: 760px) {html{font-size:118.75px;}}
@media screen and (min-width: 800px) {html{font-size:125px;}}
@media screen and (min-width: 960px) {html{font-size:150px;}}

方案2

@media screen and (min-width: 320px) {html{font-size:312.5%;}}
@media screen and (min-width: 360px) {html{font-size:351.5625%;}}
@media screen and (min-width: 375px) {html{font-size:366.211%;}}
@media screen and (min-width: 400px) {html{font-size:390.625%;}}
@media screen and (min-width: 414px) {html{font-size:404.2969%;}}
@media screen and (min-width: 440px) {html{font-size:429.6875%;}}
@media screen and (min-width: 480px) {html{font-size:468.75%;}}
@media screen and (min-width: 520px) {html{font-size:507.8125%;}}
@media screen and (min-width: 560px) {html{font-size:546.875%;}}
@media screen and (min-width: 600px) {html{font-size:585.9375%;}}
@media screen and (min-width: 640px) {html{font-size:625%;}}
@media screen and (min-width: 680px) {html{font-size:664.0625%;}}
@media screen and (min-width: 720px) {html{font-size:703.125%;}}
@media screen and (min-width: 760px) {html{font-size:742.1875%;}}
@media screen and (min-width: 800px) {html{font-size:781.25%;}}
@media screen and (min-width: 960px) {html{font-size:937.5%;}}

方案3

var designWidth = 640, rem2px = 100;
document.documentElement.style.fontSize = 
  ((window.innerWidth / designWidth) * rem2px) + 'px';

方案4

var designWidth = 640, rem2px = 100;
document.documentElement.style.fontSize = 
  ((((window.innerWidth / designWidth) * rem2px) / 16) * 100) + '%';

為了更避免理解上的混亂骚勘,我在上面js的代碼中加了 ( ) ,實(shí)際代碼中是不需要的撮奏。
詳細(xì)分析一下俏讹,rem 和 px 直接的轉(zhuǎn)換公式可以寫為:

1rem = 1 * htmlFontSize

htmlFontSize 為 html 元素的字體大小。

首先來看方案1中畜吊,在屏寬為640px情況下的設(shè)置:

@media screen and (min-width: 640px) {html{font-size:100px;}}

可以很明顯的表現(xiàn)出這一點(diǎn) 1rem = 1 * 100px 泽疆,同我們最初的設(shè)定。那么我們要得到其它屏幕大小的 htmlFontSize 值要怎么辦玲献。很簡(jiǎn)單如方案3殉疼,因?yàn)槲覀兊牟捎玫缺瓤s放的方式適配,所以計(jì)算目標(biāo)屏幕寬度和設(shè)計(jì)稿的寬度的比即可:

window.innerWidth / designWidth * rem2px + 'px'

由于瀏覽器默認(rèn)字體大小為 16px捌年,所以當(dāng)我們使用百分比作為根節(jié)點(diǎn) html 的字體大小時(shí)瓢娜,即html元素的font-size值設(shè)置為一個(gè)百分比值,rem 的計(jì)算方式就會(huì)改為:

defaultFontSize = 16px
1rem = 1 * htmlFontSize * defaultFontSize

如方案2中礼预,在屏寬為640px情況下的設(shè)置:

@media screen and (min-width: 640px) {html{font-size:625%;}}

應(yīng)用上面的公式:

1rem = 1 * 625% * 16px
其中:625% * 16 = 6.25 * 16 = 100
所以:1rem = 1 * 100px

同樣的可以得到所有屏幕大小下眠砾,html 的 font-size 值的計(jì)算公式,即為方案4:

window.innerWidth / designWidth * rem2px / 16 * 100  + '%'

通過方案3和方案4的公式托酸,就可以很方便的生成方案1和方案2中的css荠藤。

這里只給出了方案3和方案4對(duì)應(yīng)驗(yàn)證頁面(方案1和方案2是它們的變形): scheme3.html(http://htmlpreview.github.io/?https://github.com/hbxeagle/rem/blob/master/scheme3.html), scheme4.html(http://htmlpreview.github.io/?https://github.com/hbxeagle/rem/blob/master/scheme4.html

如下面兩張圖,是在屏寬為360px下的效果获高,通過計(jì)算目標(biāo)為:1rem = 56.25px。方案3設(shè)置值為:56.25px吻育,方案4設(shè)置值為:351.5625%


到目前為止貌似很完美的解決了問題念秧,實(shí)際情況當(dāng)然是出現(xiàn)了意外。在有些 Android 手機(jī)上布疼,瀏覽器或 webview 的默認(rèn)字體是隨著系統(tǒng)設(shè)置的字體改變的摊趾。這樣就會(huì)導(dǎo)致默認(rèn)字體大于或小于 16px币狠。
修改默認(rèn)字體大小后,我們?cè)倏捶桨?和方案4砾层。

同樣在屏寬為360px下漩绵,我們調(diào)大系統(tǒng)字體大小,如下面的效果

設(shè)置前 html 元素的字體大小的計(jì)算值為 18px 肛炮,設(shè)置后的計(jì)算值為 65px 止吐,由于屏幕寬度沒有改變,我們的目標(biāo)值侨糟,即我們?cè)?html 元素上設(shè)置的 font-size 值也沒有變化任然為 56.25px碍扔,而最終計(jì)算值出現(xiàn)了偏差。

分析偏差前秕重,先來看在360px屏寬下不同,方案3和方案4的計(jì)算過程:

方案3:

document.documentElement.style.fontSize = 56.25px
htmlFontSize = 56.25px
1rem = 1 * htmlFontSize = 56.25px
實(shí)際為:
1rem = 64.6875px

方案4:

document.documentElement.style.fontSize = 351.5625%
htmlFontSize = 351.5625%
defaultFontSize = 18px
1rem = 1 * htmlFontSize * defaultFontSize = 351.5625% * 18px = 63.28125px
351.5625% * 18 = 63.28125
實(shí)際為:
1rem = 64.6875px

貌似方案4的計(jì)算結(jié)果很接近實(shí)際效果,而方案3偏差很大溶耘。再來比較方案3和方案4的計(jì)算公式:

// 方案3
document.documentElement.style.fontSize = 
  window.innerWidth / designWidth * rem2px + 'px';
 
// 方案4
document.documentElement.style.fontSize = 
  window.innerWidth / designWidth * rem2px / 16 * 100 + '%';

方案4較于方案3其實(shí)多除了一個(gè)16二拐,可以推測(cè)瀏覽器在計(jì)算 rem 的具體值時(shí),如果 html 設(shè)置的 font-size 為 px 值時(shí)會(huì)先除以 16 凳兵,然后再乘htmlFontSize百新。

1rem = 1 * (56.25px / 16) * 18
1 * (56.25 / 16) * 18 = 63.28125

方案4存在問題,是因?yàn)橄到y(tǒng)的默認(rèn)字體改為了 18px 留荔,但是我們?cè)谟?jì)算百分比是時(shí)候吟孙,還是以 16px 為基準(zhǔn)值進(jìn)行計(jì)算,所以出現(xiàn)偏差(計(jì)算值和實(shí)際值之間還有一點(diǎn)偏差這個(gè)在后面會(huì)提到)聚蝶。

而在方案3中杰妓,我們其實(shí)是不考慮瀏覽器默認(rèn)字體大小的,但在實(shí)際使用的過程中碘勉,瀏覽器還是除了 16 巷挥,而此時(shí)默認(rèn)字體大小為 18px。得出如下在 html 的 fontSize 設(shè)置為 px 的情況下 rem 的計(jì)算公式為:

1rem = 1 * (htmlFontSize / 16) * defaultFontSize

在系統(tǒng)設(shè)置的字體大小發(fā)生改變時(shí)验靡,defaultFontSize 會(huì)跟著改變倍宾,而 16 不會(huì)變化。所以方案3雖然表面上不考慮默認(rèn)字體大小的變化胜嗓,只關(guān)注屏幕與設(shè)計(jì)稿之間的寬度比高职,但在實(shí)際計(jì)算中還是使用到了默認(rèn)字體大小,而且還有一個(gè)不變的 16 在作祟辞州,導(dǎo)致方案3失敗怔锌。

所謂的「root element」其實(shí)不是想象的那樣,一個(gè)是16,一個(gè)是18埃元,到底取的是那個(gè) root element 的字體大小涝涤。

ok,rem 的計(jì)算的時(shí)候岛杀,px 的方式會(huì)有一個(gè)16不隨系統(tǒng)字體大小改變阔拳,所以我們采用百分比的方案,繞開這個(gè)問題类嗤。

采用百分比的方案4因?yàn)樵谟?jì)算時(shí)寫死了默認(rèn)字體大小 16px糊肠。所以它的偏差在于沒能動(dòng)態(tài)的獲取默認(rèn)字體大小。更新如下:

方案4.1

var designWidth = 640, rem2px = 100;
var h = document.getElementsByTagName('html')[0];
var htmlFontSize = parseFloat(window.getComputedStyle(h, null)
                                    .getPropertyValue('font-size'));
 
document.documentElement.style.fontSize = 
  window.innerWidth / designWidth * rem2px / htmlFontSize * 100 + '%';

效果如下圖:

16px 的圖中土浸,設(shè)置后的 html 的 font-size 與 1rem 的實(shí)際值有偏差罪针,同時(shí) 6.4rem 的計(jì)算值也有偏差。通過查看代碼發(fā)現(xiàn)html的font-size使用的是: getPropertyValue('font-size') 而 1rem 使用的是getPropertyValue('width')黄伊,偏差出在計(jì)算 font-size 的時(shí)候?yàn)g覽器進(jìn)行了四舍五入泪酱。rem 定義中的另一個(gè)元素「font size」也不能按字面意思使用,宣告失守还最。

18px 中的偏差墓阀,以及上文中方案4在 18px 實(shí)際值和計(jì)算值出現(xiàn)的偏差都是同樣的問題。所以基準(zhǔn)值還需要修改拓轻。

在更新一版斯撮,方案4.2:

var designWidth = 640, rem2px = 100;
var d = window.document.createElement('div');
d.style.width = '1rem';
d.style.display = "none";
var head = window.document.getElementsByTagName('head')[0];
head.appendChild(d);
var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
d.remove();
document.documentElement.style.fontSize = 
  window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%';

效果如下圖:

到此為止,rem 在默認(rèn)字體不是 16px 的情況下的處理已經(jīng)解決扶叉,考慮到還有設(shè)計(jì)屏幕旋轉(zhuǎn)勿锅,最終手機(jī)端的解決方案為:

function adapt(designWidth, rem2px){
  var d = window.document.createElement('div');
  d.style.width = '1rem';
  d.style.display = "none";
  var head = window.document.getElementsByTagName('head')[0];
  head.appendChild(d);
  var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
  d.remove();
  document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%';
  var st = document.createElement('style');
  var portrait = "@media screen and (min-width: "+window.innerWidth+"px) {html{font-size:"+ ((window.innerWidth/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}";
  var landscape = "@media screen and (min-width: "+window.innerHeight+"px) {html{font-size:"+ ((window.innerHeight/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}"
  st.innerHTML = portrait + landscape;
  head.appendChild(st);
  return defaultFontSize
};
var defaultFontSize = adapt(640, 100);

回過頭來再看 rem 的定義,『The font size of the root element.』枣氧。我們以為的 root element —— html 其實(shí)還有個(gè)影子在作祟溢十,而我們以為的 font-size 其實(shí)是個(gè)近似值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末达吞,一起剝皮案震驚了整個(gè)濱河市张弛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酪劫,老刑警劉巖吞鸭,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異覆糟,居然都是意外死亡刻剥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門滩字,熙熙樓的掌柜王于貴愁眉苦臉地迎上來透敌,“玉大人盯滚,你說我怎么就攤上這事⌒锏纾” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵内列,是天一觀的道長(zhǎng)撵术。 經(jīng)常有香客問我,道長(zhǎng)话瞧,這世上最難降的妖魔是什么嫩与? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮交排,結(jié)果婚禮上划滋,老公的妹妹穿的比我還像新娘。我一直安慰自己埃篓,他們只是感情好处坪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著架专,像睡著了一般同窘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上部脚,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天想邦,我揣著相機(jī)與錄音,去河邊找鬼委刘。 笑死丧没,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锡移。 我是一名探鬼主播呕童,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼罩抗!你這毒婦竟也來了拉庵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤套蒂,失蹤者是張志新(化名)和其女友劉穎钞支,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體操刀,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烁挟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骨坑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撼嗓。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柬采,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出且警,到底是詐尸還是另有隱情粉捻,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布斑芜,位于F島的核電站肩刃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杏头。R本人自食惡果不足惜盈包,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醇王。 院中可真熱鬧呢燥,春花似錦、人聲如沸寓娩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽根暑。三九已至力试,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間排嫌,已是汗流浹背畸裳。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淳地,地道東北人怖糊。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像颇象,于是被迫代替她去往敵國和親伍伤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 了解真實(shí)的『REM』手機(jī)屏幕適配rem 作為一個(gè)低調(diào)的長(zhǎng)度單位遣钳,由于手機(jī)端網(wǎng)頁的興起扰魂,在屏幕適配中得到重用。 使用...
    張憲宇閱讀 2,230評(píng)論 0 5
  • rem 作為一個(gè)低調(diào)的長(zhǎng)度單位蕴茴,由于手機(jī)端網(wǎng)頁的興起劝评,在屏幕適配中得到重用。使用 rem 前端開發(fā)者可以很方便的在...
    layjoy閱讀 1,164評(píng)論 1 17
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案倦淀? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,728評(píng)論 1 92
  • 這一天路是沒少跑蒋畜, 風(fēng)景卻沒看到, 只是混了個(gè)頓頓酒足飯飽撞叽。 這也是旅途的一大樂趣姻成, 最起碼對(duì)得起了五臟廟插龄。 美景...
    琢玉書生閱讀 431評(píng)論 0 1
  • 1 、編譯內(nèi)核 參考 上節(jié)內(nèi)容準(zhǔn)備好 mkimage 文件,將其復(fù)制到/usr/bin/目錄下(使用 ZLG 官網(wǎng)...
    不配野心閱讀 659評(píng)論 4 1