手機(jī)端頁(yè)面自適應(yīng)解決方案—rem布局進(jìn)階版(附源碼示例)

一年前筆者寫了一篇 《手機(jī)端頁(yè)面自適應(yīng)解決方案—rem布局》,意外受到很多朋友的關(guān)注和喜歡。但隨著時(shí)間的推移,該方案已然過時(shí),故為大家介紹一個(gè)目前我極力推薦使用的,更加完美的方案——rem布局(進(jìn)階版)

另外:
  • 此方案僅適用于移動(dòng)端web
  • 文章底部常見問題說(shuō)明第四條,筆者已給出一個(gè)相當(dāng)便捷的解決方案,歡迎留言交流。(2017/9/9)

該方案使用相當(dāng)簡(jiǎn)單,把下面這段已壓縮過的 原生JS(僅1kb,源碼已在文章底部更新,2017/5/3) 放到 HTML 的 head 標(biāo)簽中即可(注:不要手動(dòng)設(shè)置viewport,該方案自動(dòng)幫你設(shè)置)

<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>

代碼原理

這是阿里團(tuán)隊(duì)的高清方案布局代碼,所謂高清方案就是利用rem的特性(我們知道默認(rèn)情況下html的1rem = 16px),根據(jù)設(shè)備屏幕的DPR(設(shè)備像素比,又稱DPPX,比如dpr=2時(shí),表示1個(gè)CSS像素由4個(gè)物理像素點(diǎn)組成)根據(jù)設(shè)備DPR動(dòng)態(tài)設(shè)置 html 的font-size為(50 * dpr),同時(shí)調(diào)整頁(yè)面的壓縮比率(即:1/dpr),進(jìn)而達(dá)到高清效果

有何優(yōu)勢(shì)

  • 引用簡(jiǎn)單,布局簡(jiǎn)便
  • 根據(jù)設(shè)備屏幕的DPR,自動(dòng)設(shè)置最合適的高清縮放。
  • 保證了不同設(shè)備下視覺體驗(yàn)的一致性。(老方案是,屏幕越大元素越大;此方案是,屏幕越大,看的越多)
  • 有效解決移動(dòng)端真實(shí)1px問題(這里的1px 是設(shè)備屏幕上的物理像素)

如何使用

重要的事情說(shuō)三遍!
絕不是每個(gè)地方都要用rem,rem只適用于固定尺寸!
絕不是每個(gè)地方都要用rem,rem只適用于固定尺寸!
絕不是每個(gè)地方都要用rem,rem只適用于固定尺寸!
在相當(dāng)數(shù)量的布局情境中(比如底部導(dǎo)航元素平分屏幕寬,大尺寸元素),你必須使用百分比或者flex才能完美布局!
看過 《手機(jī)端頁(yè)面自適應(yīng)解決方案—rem布局》的朋友,應(yīng)該對(duì)rem有所了解,這里不再贅述,
此方案也是默認(rèn) 1rem = 100px,所以你布局的時(shí)候,完全可以按照設(shè)計(jì)師給你的效果圖寫各種尺寸啦。
比如你在效果圖上量取的某個(gè)按鈕元素長(zhǎng) 55px, 寬37px ,那你直接可以這樣寫樣式:

.myBtn {
   width: 0.55rem;
   height: 0.37rem;
}

rem布局(進(jìn)階版)實(shí)踐應(yīng)用

iPhone5 下頁(yè)面效果.png
iPhone 6 Plus 下頁(yè)面效果.png

為了讓朋友們更清晰感受此方案的巨大優(yōu)勢(shì),下面是源碼和Demo

實(shí)踐應(yīng)用1(請(qǐng)?jiān)谑謾C(jī)端或者手機(jī)模式下瀏覽效果更佳!)
實(shí)踐應(yīng)用2(請(qǐng)?jiān)谑謾C(jī)端或者手機(jī)模式下瀏覽效果更佳?。?/a>
線上項(xiàng)目(請(qǐng)?jiān)谑謾C(jī)端或者手機(jī)模式下瀏覽效果更佳?。?/a>
示例源碼
在線Demo

常見問題說(shuō)明,新手很有必要看一下(2017/1/19)

許多同學(xué)對(duì)該方案存在不少誤解導(dǎo)致使用出現(xiàn)各種問題,這里統(tǒng)一回復(fù)下。

1.問:為啥手機(jī)網(wǎng)頁(yè)效果圖寬度是要640或者750的,我非得弄個(gè)666的不行咩?

答:老實(shí)說(shuō)當(dāng)然可以,不過為了規(guī)范,640或者750是相對(duì)合適的。
拿Iphone 5s 舉例,它的css像素寬度是320px,由于它的dpr=2,所以它的物理像素寬度為320 × 2 = 640px,這也就是為什么,你在5s上截了一張圖,在電腦上打開,它的原始寬度是640px的原因。
那 iphone 6 的截圖寬度呢? 375 × 2 = 750
那 iphone 6 sp 的截圖寬度呢? 414 × 3 = 1242
以此類推,你現(xiàn)在能明白效果圖為什么一般是 640 ,750 甚至是 1242 的原因了么?(真沒有歧視安卓機(jī)的意思。。。)

2.問:寬度用rem寫的情況下, 在 iphone6 上沒問題, 在 iphone5上會(huì)有橫向滾動(dòng)條,何解?

答:假設(shè)你的效果圖寬度是750,在這個(gè)效果圖上可能有一個(gè)寬度為7rem(高清方案默認(rèn) 1rem = 100px)的元素。我們知道,高清方案的特點(diǎn)就是幾乎完美還原效果圖,也就是說(shuō),你寫了一個(gè)寬度為 7rem 的元素,那么在目前主流移動(dòng)設(shè)備上都是7rem。然而,iphone 5 的寬度為640,也就是6.4rem。于是橫向滾動(dòng)條不可避免的出現(xiàn)了。
怎么辦呢? 這是我目前推薦的比較安全的方式:如果元素的寬度超過效果圖寬度的一半(效果圖寬為640或750),果斷使用百分比寬度,或者flex布局。就像把等屏寬的圖片寬度設(shè)為100%一樣。

3.問:不是 1rem = 100px嗎,為什么我的代碼寫了一個(gè)寬度為3rem的元素,在電腦端的谷歌瀏覽器上寬度只有150px?

答:先說(shuō)高清方案代碼,再次強(qiáng)調(diào)咱們的高清方案代碼是根據(jù)設(shè)備的dpr動(dòng)態(tài)設(shè)置html 的 font-size,
如果dpr=1(如電腦端),則html的font-size為50px,此時(shí) 1rem = 50px
如果dpr=2(如iphone 5 和 6),則html的font-size為100px,此時(shí) 1rem = 100px
如果dpr=3(如iphone 6 sp),則html的font-size為150px,此時(shí) 1rem = 150px
如果dpr為其他值,即便不是整數(shù),如3.4 , 也是一樣直接將dpr 乘以 50 。

再來(lái)說(shuō)說(shuō)效果圖,一般來(lái)講,我們的效果圖寬度要么是640,要么是750,無(wú)論哪一個(gè),它們對(duì)應(yīng)設(shè)備的dpr=2,此時(shí),1 rem = 50 × 2 = 100px。這也就是為什么高清方案默認(rèn)1rem = 100px。而將1rem默認(rèn)100px也是好處多多,可以幫你快速換算單位,比如在750寬度下的效果圖,某元素寬度為53px,那么css寬度直接設(shè)為53/100=0.53rem了。

然而極少情況下,有設(shè)計(jì)師將效果圖寬定為1242px,因?yàn)樗掷镏挥幸粋€(gè)iphone 6 sp (dpr = 3),設(shè)計(jì)完效果圖剛好可以在他的iphone 6 sp里查看調(diào)整。一切完畢之后,他將這個(gè)效果圖交給你來(lái)切圖。由于這個(gè)效果圖對(duì)應(yīng)設(shè)備的dpr=3,也就是1rem = 50 × 3 = 150px。所以如果你量取了一個(gè)寬度為90px的元素,它的css寬度應(yīng)該為 90/150=0.6rem。由于咱們的高清方案默認(rèn)1rem=100px,為了還原效果圖,你需要這樣換算。當(dāng)然,一個(gè)技巧就是你可以直接修改咱們的高清方案的默認(rèn)設(shè)置。在代碼的最后 你會(huì)看到 flex(false, 100, 1) ,將其修改成flex(false, 66.66667, 1)(感謝簡(jiǎn)友:V旅行指出此處錯(cuò)誤! 2017/3/24)就不用那么麻煩的換算了,此時(shí)那個(gè)90px的直接寫成0.9rem就可以了。

4.問:在此方案下,我如果引用了別的UI庫(kù),那些UI庫(kù)的元素會(huì)顯得特別小,如何解決?

答:可以這樣去理解問題的原因,如果不用高清方案,別的UI庫(kù)的元素在移動(dòng)設(shè)備上(假設(shè)這個(gè)設(shè)備是iphone 5好了)顯示是正常的,這沒有問題,然后我們?cè)谶@個(gè)設(shè)備上將該頁(yè)面截圖放到電腦上看,發(fā)現(xiàn)寬度是640(問答1解釋過了),根據(jù)你的像素眼大致測(cè)量,你發(fā)現(xiàn)這個(gè)設(shè)備上的某個(gè)字體大小應(yīng)該是12px,而你在電腦上測(cè)量應(yīng)該是24px。

現(xiàn)在我們使用高清方案去還原這個(gè)頁(yè)面,那么字體大小應(yīng)該寫為 0.24rem 才對(duì)!

所以,如果你引用了其他的UI庫(kù),為了兼容高清方案,你需要對(duì)該UI庫(kù)里凡是應(yīng)用px的地方做相應(yīng)處理,即: a px => a*0.02 rem
(具體處理方式因人而異,有模塊化開發(fā)經(jīng)驗(yàn)的同學(xué)可使用類似的 px2rem 的插件去轉(zhuǎn)化,也可以完全手動(dòng)處理)


(2017/9/9更新)然而真實(shí)情況往往更為復(fù)雜,比如,你引入了百度地圖(N個(gè)樣式需要處理轉(zhuǎn)換);或者你引入了一個(gè)
framework;又或者你使用了 video 標(biāo)簽,上面默認(rèn)的尺寸樣式很難處理。等等這些棘手問題

面對(duì)這些情況,此時(shí)我們的高清方案如果不再壓縮頁(yè)面,那么以上問題將迎刃而解。
基于這樣的思路,筆者對(duì)高清方案的源碼做了如下修改,即添加一個(gè)叫做 normal 的參數(shù),由它來(lái)控制頁(yè)面是否壓縮。
在文章頂部代碼的最后,你會(huì)看到 flex(false, 100, 1),默認(rèn)情況下頁(yè)面是開啟壓縮的。

如果你需要禁止壓縮,由于我們的源碼執(zhí)行后,直接將flex函數(shù)掛載到全局變量window上了,此時(shí)你直接在需要禁止壓縮的頁(yè)面執(zhí)行 window.flex(true) 就可以了,而rem的用法保持不變。

有一點(diǎn)美中不足的是,如果禁止了頁(yè)面壓縮,高清屏的1像素就不能實(shí)現(xiàn)了,如果你必須要實(shí)現(xiàn)1像素,那么自行谷歌:css 0.5像素,有N多的解決方案,這里不再贅述。

5.問:有時(shí)候字體會(huì)不受控制的變大,怎么辦?

答:在X5新內(nèi)核Blink中,在排版頁(yè)面的時(shí)候,會(huì)主動(dòng)對(duì)字體進(jìn)行放大,會(huì)檢測(cè)頁(yè)面中的主字體,當(dāng)某一塊字體在我們的判定規(guī)則中,認(rèn)為字號(hào)較小,并且是頁(yè)面中的主要字體,就會(huì)采取主動(dòng)放大的操作。然而這不是我們想要的,可以采取給最大高度解決

解決方案:

*, *:before, *:after { max-height: 100000px }

補(bǔ)充:有同學(xué)反映,在一些情況下 textarea 標(biāo)簽內(nèi)的字體大小即便加上上面的方案,字體也會(huì)變大,無(wú)法控制。此時(shí)你需要給 textareadisplay 設(shè)為 table 或者 inline-table 即可恢復(fù)正常。(感謝 程序媛喵喵 對(duì)此的補(bǔ)充!2017/7/7)

6.問:我在底部導(dǎo)航用的flex感覺更合適一些,請(qǐng)問這樣子混著用可以嗎?

答:咱們的rem適合寫固定尺寸。其余的根據(jù)需要換成flex或者百分比。源碼示例中就有這三種的綜合運(yùn)用。

7.問:在高清方案下,一個(gè)標(biāo)準(zhǔn)的,較為理想的寬度為640的頁(yè)面效果圖應(yīng)該是怎樣的?

點(diǎn)擊瀏覽:一個(gè)標(biāo)準(zhǔn)的640手機(jī)頁(yè)面設(shè)計(jì)稿參考(沒錯(cuò),在此方案中,你可以完全按照這張?jiān)O(shè)計(jì)稿的尺寸寫布局了。就是這么簡(jiǎn)單?。?/p>

8.問:用了這個(gè)方案如何使用媒體查詢呢?

一般來(lái)講,使用了這個(gè)方案是沒必要用媒體查詢了,如果你必須要用,假設(shè)你要對(duì) iphone5 (css像素寬度320px,
這里需要取其物理像素,也就是640)寬度下的類名做處理,你可以這樣

@media screen and (max-width: 640px) {
    .yourLayout {
        width:100%;
    }
}
9.問:可以提供下這個(gè)高清方案的源碼嗎?
'use strict';

/**
 * @param {Boolean} [normal = false] - 默認(rèn)開啟頁(yè)面壓縮以使頁(yè)面高清;  
 * @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`;
};
10.問:我在使用 rem 布局進(jìn)階方案的時(shí)候遇到了XXX的問題,如何解決?
  • 此方案久經(jīng)考驗(yàn),具有普遍適用性,自身出致命問題的情況很少,至少筆者是沒遇到過。
  • 絕大多數(shù)你遇到的問題,都是由于對(duì)rem布局理解不到位導(dǎo)致的。本文對(duì)rem布局做了大量的解釋說(shuō)明,配置了若干 demo,你可以把你遇到的問題放到demo里測(cè)試。遇到問題時(shí),首先問自己,為什么這明顯的錯(cuò)誤大家沒遇到就我遇到了??
  • 如果你真的經(jīng)過充分驗(yàn)證,比對(duì),確實(shí)是rem布局自身出了問題,那么請(qǐng)私信我,把還原問題場(chǎng)景的 demo 或者文件發(fā)給我。謝謝!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容