1. 程式人生 > >h5 手機螢幕適配—REM

h5 手機螢幕適配—REM

一、rem、em和px之間的關係

    使用rem之前,先得弄清楚rem、em和px之間的關係,特別是每一個單位的使用跟程式碼塊的繼承之間的關係:

    
    通過對比會發現:只是單位使用不一樣但效果卻是截然不同的。rem和em都是相對單位,px則不是

    在CSS樣式表中,單位em是作為字型高度的單位來使用的,但實際字型大小的高度顯示是使用者對DPI的定義來決定的。為了改善這種樣式規則,單位rem則是直接取決於文件根元素字型預設大小,也可以理解為root em,跟em有所不同的是使用rem單位的字型大小在整個文件中都是恆定不變的

二、初識rem

    rem 的官方定義:The font size of the root element,即以根節點的字型大小作為基準值進行長度計算。

    rem的相容性:
    
rem的相容性
    
通過上圖可見:

  • 大部分主流瀏覽器支援,但要注意IE9、IE10
  • iOS:6.1系統以上都支援
  • Android:2.1系統以上都支援

三、rem 為解決什麼問題而存在

    先不談安卓,就以iphone各種手機的尺寸來說,iPhone4,5 寬度320px,iPhone6 375px,iPhone6 plus 414px. iPad 768px。如果一個按鈕,固定一個75x25的尺寸,那麼就必然會導致在不同尺寸下的相對大小不一樣。這帶來的問題就在於會直接影響到設計的美觀,可能在iPhone6下,一個完美的設計圖,到了iPhone5,就變得low很多。為了讓頁面元素的尺寸能夠在裝置寬度變化的時候也跟著變化,rem就出現了

    使用rem適配的原理就是我們只需要在裝置寬度大小變化的時候,調整html的字型大小,那麼頁面上所有使用rem單位的元素都會相應的變化。 這也是rem與px最大的區別。

四、利用meta標籤對viewport進行控制

    在講述rem佈局之前得先搞明白的就是移動裝置上的viewport,只有明白了viewport的概念以及弄清楚了跟viewport有關的meta標籤的使用,才能更好地讓我們的網頁適配或響應各種不同解析度的移動裝置。

1、viewport的概念

    通俗的講,移動裝置上的viewport就是裝置的螢幕上能用來顯示我們的網頁的那一塊區域,即瀏覽器上(也可能是一個app中的webview)用來顯示網頁的那部分割槽域,但viewport又不侷限於瀏覽器可視區域的大小,它可能比瀏覽器的可視區域要大,也可能比瀏覽器的可視區域要小。
    下圖列出了一些裝置上瀏覽器預設的viewport寬度。
    
瀏覽器預設的viewport寬度

2、css中的1px並不等於裝置的1px

    css中的畫素只是一個抽象的單位,在不同的裝置或不同的環境中,css中的1px所代表的裝置物理畫素是不同的。
    在移動端瀏覽器中以及某些桌面瀏覽器中,window物件有一個devicePixelRatio屬性,它的官方的定義為:

window.devicePixelRatio is the ratio between physical pixels and device-independent pixels (dips) on the device.
window.devicePixelRatio = physical pixels / dips

裝置物理畫素和裝置獨立畫素的比例,也就是 devicePixelRatio = 物理畫素 / 獨立畫素。css中的px就可以看做是裝置的獨立畫素,所以通過devicePixelRatio,我們可以知道該裝置上一個css畫素代表多少個物理畫素。
    需要注意的是,devicePixelRatio存在相容性問題,具體可以檢視PPK made some research on devicePixelRatio
    
拓展:PPK的關於三個viewport的理論
    ppk把移動裝置上的viewport分為layout viewport 、 visual viewport 和 ideal viewport 三類,其中的ideal viewport是最適合移動裝置的viewport,ideal viewport的寬度等於移動裝置的螢幕寬度,只要在css中把某一元素的寬度設為ideal viewport的寬度(單位用px),那麼這個元素的寬度就是裝置螢幕的寬度了,也就是寬度為100%的效果。ideal viewport 的意義在於,無論在何種解析度的螢幕下,那些針對ideal viewport 而設計的網站,不需要使用者手動縮放,也不需要出現橫向滾動條,都可以完美的呈現給使用者。

類別 簡述 說明 圖例
layout viewport 瀏覽器預設的viewport layout viewport 的寬度是大於瀏覽器可視區域的寬度的 layout viewport
visual viewport 瀏覽器可視區域的大小 在Android 2, Oprea mini 和 UC 8中無法正確獲取 visual viewport
ideal viewport 移動裝置的理想viewport 理想適配指的是:1、不需要使用者縮放和橫向滾動條就能正常的檢視網站的所有內容;2、顯示的文字的大小是合適,無論是在何種密度螢幕,何種解析度下,顯示出來的大小都是差不多的。當然,不只是文字,其他元素如圖片等也如此 ideal viewport1ideal viewport2

3、利用meta標籤對viewport進行控制

在蘋果的規範中,meta viewport 有6個指令,[可以同時使用,也可以單獨使用或混合使用,多個指令同時使用時用逗號隔開]如下:

指令 說明
width 設定layout viewport 的寬度,為一個正整數,或字串”width-device”
initial-scale 設定頁面的初始縮放值,為一個數字,可以帶小數
minimum-scale 允許使用者的最小縮放值,為一個數字,可以帶小數
maximum-scale 允許使用者的最大縮放值,為一個數字,可以帶小數
height 設定layout viewport 的高度,這個屬性對我們並不重要,很少使用
user-scalable 是否允許使用者進行縮放,值為”no”或”yes”, no 代表不允許,yes代表允許

    
    要把當前的viewport寬度設為ideal viewport的寬度,既可以設定 width=device-width,也可以設定 initial-scale=1,但這兩者各有一個小缺陷,就是iphone、ipad以及IE 會橫豎屏不分[下圖所示],通通以豎屏的ideal viewport寬度為準。所以,最完美的寫法應該是,兩者都寫上去,這樣就initial-scale=1解決了 iphone、ipad的毛病,width=device-width則解決了IE的毛病:

<meta name="viewport" content="width=device-width, initial-scale=1">
//注意:縮放是相對於ideal viewport來縮放的,縮放值越大,當前viewport的寬度就會越小,反之亦然

五、rem佈局

rem佈局基本概念

    rem佈局就是指為文件的根節點元素設定一個基準字型大小,然後所有的元素尺寸都以rem為單位來寫,為了能夠在不同尺寸的手機螢幕上自適應,需要用js來判斷手機寬度,並動態設定<html>的字型大小,這樣基準字型變了,元素的尺寸自然相應變化,達到了自適應的效果。

    廢話說了辣麼多,這裡給大家介紹一個移動端佈局開發解決方案

  • 使用動態的HTML根字型大小和動態的viewport scale。

  • 遵循視覺一致性原則。在不同大小的螢幕和不同的裝置畫素密度下,讓你的頁面看起來是一樣的。

  • 不僅便捷了你的佈局,同時它使用起來異常簡單。

優勢
  • 保證不同裝置下的統一視覺體驗。
  • 不需要你再手動設定viewport,根據當前環境計算出最適合的viewport
  • 支援任意尺寸的設計圖,不侷限於特定尺寸的設計圖。
  • 支援單一專案,多種設計圖尺寸,專為解決大型,長週期專案。
  • 提供px2rem轉換方法,CSS佈局,零成本轉換,原始值不丟失。
  • 有效解決移動端真實1畫素問題。
用法
1、 引入hotcss.js
<script src="/path/to/hotcss.js"></script>
2、css要怎麼寫

hotcss提供的將px轉為rem的方法,可根據您的需要選擇使用。

//px2rem.scss
@function px2rem( $px ){
    @return $px*320/$designWidth/20 + rem;
}

推薦使用scss來編寫css,在scss檔案的頭部使用importpx2rem匯入,

@import '/path/to/px2rem.scss';

如果你的專案是單一尺寸設計圖,那麼你需要去px2rem.scss中定義全域性的designWidth

@function px2rem( $px ){
    @return $px*320/$designWidth/20 + rem;
}
$designWidth : 750; //如設計圖是750

如果你的專案是多尺寸設計圖,那麼就不能定義全域性的designWidth了。需要在你的業務scss中單獨定義。如以下是style.scss

@import '/path/to/px2rem.scss';
$designWidth : 750; //如設計圖是750

$designWidth必須要在使用px2rem前定義。否則scss編譯會出錯。

注意:如果使用less,則需要引入less-plugin-functions,普通的less編譯工具無法正常編譯。

3、想用px怎麼辦?

直接寫px肯定是不能適配的,那hotcss.js會在html上註冊data-dpr屬性,這個屬性用來標識當前環境dpr值。那麼要使用px可以這麼寫。

//scss寫法
#container{
    font-size: 12px ;
    [data-dpr="2"] &{
        font-size: 24px;
    }
    [data-dpr="3"] &{
        font-size: 36px;
    }
}

可能你會說 talk is cheap,show me the code,那我現在列下hotcss整個專案的目錄結構。

├── example //所有的示例都在這個目錄下
│   ├── duang
│   ├── normal
│   └── wolf
│
└── src //主要檔案在這裡
    ├── hotcss.js
    ├── px2rem.less
    ├── px2rem.scss
    └── px2rem.styl
  • hotcss.js
(function( window , document ){

    'use strict';

    //給hotcss開闢個名稱空間,別問我為什麼,我要給你準備你會用到的方法,免得用到的時候還要自己寫。
    var hotcss = {};

    (function() {
        //根據devicePixelRatio自定計算scale
        //可以有效解決移動端1px這個世紀難題。
        var viewportEl = document.querySelector('meta[name="viewport"]'),
            hotcssEl = document.querySelector('meta[name="hotcss"]'),
            dpr = window.devicePixelRatio || 1,
            maxWidth = 540,
            designWidth = 0;

        dpr = dpr >= 3 ? 3 : ( dpr >=2 ? 2 : 1 );

        //允許通過自定義name為hotcss的meta頭,通過initial-dpr來強制定義頁面縮放
        if (hotcssEl) {
            var hotcssCon = hotcssEl.getAttribute('content');
            if (hotcssCon) {
                var initialDprMatch = hotcssCon.match(/initial\-dpr=([\d\.]+)/);
                if (initialDprMatch) {
                    dpr = parseFloat(initialDprMatch[1]);
                }
                var maxWidthMatch = hotcssCon.match(/max\-width=([\d\.]+)/);
                if (maxWidthMatch) {
                    maxWidth = parseFloat(maxWidthMatch[1]);
                }
                var designWidthMatch = hotcssCon.match(/design\-width=([\d\.]+)/);
                if (designWidthMatch) {
                    designWidth = parseFloat(designWidthMatch[1]);
                }
            }
        }

        document.documentElement.setAttribute('data-dpr', dpr);
        hotcss.dpr = dpr;

        document.documentElement.setAttribute('max-width', maxWidth);
        hotcss.maxWidth = maxWidth;

        if( designWidth ){
            document.documentElement.setAttribute('design-width', designWidth);
            hotcss.designWidth = designWidth;
        }

        var scale = 1 / dpr,
            content = 'width=device-width, initial-scale=' + scale + ', minimum-scale=' + scale + ', maximum-scale=' + scale + ', user-scalable=no';

        if (viewportEl) {
            viewportEl.setAttribute('content', content);
        } else {
            viewportEl = document.createElement('meta');
            viewportEl.setAttribute('name', 'viewport');
            viewportEl.setAttribute('content', content);
            document.head.appendChild(viewportEl);
        }

    })();

    hotcss.px2rem = function( px , designWidth ){
        //預判你將會在JS中用到尺寸,特提供一個方法助你在JS中將px轉為rem。就是這麼貼心。
        if( !designWidth ){
            //如果你在JS中大量用到此方法,建議直接定義 hotcss.designWidth 來定義設計圖尺寸;
            //否則可以在第二個引數告訴我你的設計圖是多大。
            designWidth = parseInt(hotcss.designWidth , 10);
        }

        return parseInt(px,10)*320/designWidth/20;
    }

    hotcss.rem2px = function( rem , designWidth ){
        //新增一個rem2px的方法。用法和px2rem一致。
        if( !designWidth ){
            designWidth = parseInt(hotcss.designWidth , 10);
        }
        //rem可能為小數,這裡不再做處理了
        return rem*20*designWidth/320;
    }

    hotcss.mresize = function(){
        //給HTML設定font-size。
        var innerWidth = document.documentElement.getBoundingClientRect().width || window.innerWidth;

        if( hotcss.maxWidth && (innerWidth/hotcss.dpr > hotcss.maxWidth) ){
            innerWidth = hotcss.maxWidth*hotcss.dpr;
        }

        if( !innerWidth ){ return false;}

        document.documentElement.style.fontSize = ( innerWidth*20/320 ) + 'px';

        hotcss.callback && hotcss.callback();

    };

    hotcss.mresize(); 
    //直接呼叫一次

    window.addEventListener( 'resize' , function(){
        clearTimeout( hotcss.tid );
        hotcss.tid = setTimeout( hotcss.mresize , 33 );
    } , false ); 
    //繫結resize的時候呼叫

    window.addEventListener( 'load' , hotcss.mresize , false ); 
    //防止不明原因的bug。load之後再呼叫一次。


    setTimeout(function(){
        hotcss.mresize(); 
        //防止某些機型怪異現象,非同步再呼叫一次
    },333)

    window.hotcss = hotcss; 
    //名稱空間暴露給你,控制權交給你,想怎麼調怎麼調。


})( window , document );

栗子:

//.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>這是一個最最普通的演示</title>
    <!--
    根據頁面渲染機制,`hotcss.js`必須在其他JS載入前載入,萬不可非同步載入。

    如果可以,你應將`hotcss.js`的內容以內嵌的方式寫到`<head>`標籤裡面進行載入,並且保證在其他js檔案之前。

    為了避免不必要的bug,請將CSS放到該JS之前載入。
    -->
    <link rel="stylesheet" href="./css/style.css">
    <script src="../hotcss.js"></script>
</head>
<body>
    <div id="header">普通的演示</div>
    <div id="container1">
        這是一個普通的演示。在head頭載入hotcss.js。
        優雅的使用scss書寫css。單位均使用了px2rem處理
    </div>
    <div id="container2">
        這個裡面的字型沒有使用px2rem。具體寫法參看css。
        如果你在使用chrome。請開啟控制檯,嘗試模擬不同裝置,來查看錶現。
    </div>
</body>
</html>
//.scss
@import 'px2rem'; //第一步先把px2rem匯入

$designWidth : 640; //寫scss之前,必須要先定義designWidth。


body{
    width: 16rem;
    margin: 0 auto;
    padding: 0;
}

#header{
    width: px2rem(640);
    height: px2rem(88);
    line-height: px2rem(88);
    background-color: #33aa33;
    text-align: center;
    font-size: px2rem(48);
    color: rgba(255,255,255,1);
}

#container1{
    margin-top: px2rem(10);
    padding: px2rem(30);
    background-color: #191919;
    color: #f0f0f0;
    line-height: 1.7;
    max-height: 100%;
    font-size: px2rem(32);
}

#container2{
    margin-top: px2rem(10);
    padding: px2rem(30);
    background-color: #191919;
    color: #f0f0f0;
    line-height: 1.7;
    max-height: 100%;
    font-size: 12px ;
    [data-dpr="2"] &{
        font-size: 24px;
    }
    [data-dpr="3"] &{
        font-size: 36px;
    }
}
介面說明

initial-dpr

可以通過強制設定dpr。來關閉響應的viewport scale。使得viewport scale始終為固定值。

<meta name="hotcss" content="initial-dpr=1">
<script src="/path/to/hotcss.js"></script>
<!--
如iphone微信強設dpr=1,則可以長按識別二維碼。
注意,強制設定dpr=1後,css中的1px在2x,3x屏上則不再是真實的1px。
-->

max-width

通過設定該值來優化平板/PC訪問體驗,注意該值預設值為540。設定為0則該功能關閉。
為了配合使用該設定,請給body增加樣式width:16rem;margin:0 auto;

<meta name="hotcss" content="max-width=640">
<script src="/path/to/hotcss.js"></script>
<!--
預設為540,可根據具體需求自己定義
-->
<style>
body{
    width: 16rem;
    margin: 0 auto;
}
</style>

design-width

通過對design-width的設定可以在本頁執行的JS中直接使用hotcss.px2rem/hotcss.rem2px方法,無需再傳遞第二個值。

<meta name="hotcss" content="design-width=750">
<script src="/path/to/hotcss.js"></script>

hotcss.mresize

用於重新計算佈局,一般不需要你手動呼叫。

hotcss.mresize();

hotcss.callback

觸發mresize的時候會執行該方法。

hotcss.callback = function(){
  //your code here
}

單位轉換hotcss.px2rem/hotcss.rem2px

hotcss.px2remhotcss.rem2px你可以預先設定hotcss.designWidth可以在meta中設定design-width,則之後使用這兩個方法不需要再傳遞第二個引數。

迭代後仍然支援在js中設定hotcss.designWidth,推薦使用meta去設定。

/**
* [px2rem px值轉換為rem值]
* @param  {[number]} px          [需要轉換的值]
* @param  {[number]} designWidth [設計圖的寬度尺寸]
* @return {[number]}             [返回轉換後的結果]
*/
hotcss.px2rem( px , designWidth );

/**
* 同上。
* 注意:因為rem可能為小數,轉換後的px值有可能不是整數,需要自己手動處理。
*/
hotcss.rem2px( rem , designWidth );


//你可以在meta中定義design-width,此後使用px2rem/rem2px,就不需要傳遞designWidth值了。同時也支援舊的設定方式,直接在JS中設定hotcss.designWidth
hotcss.px2rem(200);
hotcss.rem2px(350);

六、使用rem的線上站點

使用rem的大站

使用hotcss的站點

七、參考連結