1. 程式人生 > >media query ie8- 相容實現總結

media query ie8- 相容實現總結

雖然說響應式設計的理想狀態是,需對pc/移動各種終端進行響應;但是現實是高解析度的pc端與手機終端螢幕相差太大,像電商這樣有大量圖片和文字資訊的同時排版要求精準的頁面,設計一個同時適應高解析度pc又適合小尺寸的手機終端是挑戰;同時高解析度下pc頁面資訊量巨大,對於手機端使用者是否需要,也許會造成頻寬浪費;再者手機終端和pc終端的使用者操作習慣也相差甚大,這種多圖多資訊量要求精準的頁面,設計出來恐怕會是2個完全不同的版本,也許各自維護更方便。由於業務形態原因,隨著使用者解析度的提高,1024×768已不再是主流,寬屏使用者比例越來越大,因此我們的響應式考慮如何充分利用PC使用者裝置上更多空間而設計。下圖為淘寶使用者的螢幕解析度和瀏覽器比例,鑑於ie8-瀏覽器目前佔比約70%,media query的ie8-相容迫於現實還是要做,淚……

media query簡介

1.link標籤方式

<link rel="stylesheet" type="text/css" media="screen" href="sans-serif.css">
<link rel="stylesheet" type="text/css" media="print" href="serif.css">

2.css方式

@media screen {
  * { font-family: sans-serif }
}

媒體型別有很多種:‘aural’, ‘braille’, ‘handheld’, ‘print’, ‘projection’, ‘screen’, ‘tty’, ‘tv’、‘embossed’、 ‘speech’、’3d-glasses’,但最常用的是screen和print,對於前端們來講最常用的應該只有screen了。應用於所有媒體型別可以用all,省略不寫預設就是all。media query支援很多表達式,常用的如下, 

完整的檢視這裡 :

@media all and (min-width: 400px) and (max-width: 700px) { /*螢幕寬度在[400px,700]之間時,應用該css*/ }
@media all and (orientation: portrait) {  /*裝置豎屏時*/  }
@media and (min-device-width: 800px) { /*最小裝置寬度為800px時*/ }

利用media query可以輕鬆實現不同螢幕寬度時切換不同的頁面佈局,但是很不幸ie8及以下都還不支援media query,於是開始了下面的media query相容之旅……

目前實現media query ie相容的庫比較成熟的有 

respond.js 和 css3-mediaqueries-js ; 它們各有優劣 ,respond.js壓縮後1k,只實現了media query中最常用的min-width max-width的相容;css3-mediaqueries-js基本實現了所有css3規範中的media query特性的相容,所以導致壓縮有16k,測試反饋其效能遠低於respond.js;不過確實一淘首頁2次響應式設計均只需用到max-width和min-width, Modernizr 和 H5BP 也均推薦使用respond.js,下面具體看看它們的實現吧

respond.js原始碼分析

使用方式
1.在css中正常用 min/max-width media queries
    @media screen and (min-width: 480px){
        ...styles for 480px and up go here
    }
2.引入respond.min.js,但要在css的後面(越早引入越好,在ie下面看到頁面閃屏的概率就越低,因為最初css會先渲染出來,如果respond.js載入得很後面,這時重新根據media query解析出來的css會再改變一次頁面的佈局等,所以看起來有閃屏的現象)

實現思路

  • 1.把head中所有<link rel=“sheetstyle” href=“xx”/>的css路徑取出來放入陣列
  • 2.然後遍歷陣列一個個發ajax請求
  • 3.ajax回撥後僅分析response中的media query的min-width和max-width語法,分析出viewport變化區間對應相應的css塊
  • 4.頁面初始化時和window.resize時,根據當前viewport使用相應的css塊。
window.matchMedia = window.matchMedia || (function(doc, undefined){
  var bool,
      docElem  = doc.documentElement,
      refNode  = docElem.firstElementChild || docElem.firstChild,
      // fakeBody required for <FF4 when executed in <head>
      fakeBody = doc.createElement('body'),
      div      = doc.createElement('div');
  div.id = 'mq-test-1';
  div.style.cssText = "position:absolute;top:-100em";
  fakeBody.style.background = "none";
  fakeBody.appendChild(div);
  return function(q){
    div.innerHTML = '&shy;<style media="'+q+'"> #mq-test-1 { width: 42px; }</style>';
    docElem.insertBefore(fakeBody, refNode);
    bool = div.offsetWidth == 42;
    docElem.removeChild(fakeBody);
    return { matches: bool, media: q };
  };
})(document);
//檢測是否支援media query,檢測css是否有效的方法都差不多,建立一個元素應用該css後檢測元素寬度,然後清除該元素。
.......
if( !!href && isCSS && !parsedSheets[ href ] ){
    // selectivizr exposes css through the rawCssText expando
    if (sheet.styleSheet && sheet.styleSheet.rawCssText) {
                //sheet.styleSheet.rawCssText看不懂,原來是方便selectivizr和respond.js聯用,http://selectivizr.com/tests/respond/
                //selectivizr的作用是 CSS3 selectors for IE;約定將原csstext放在styleSheet的link上的擴充套件屬性rawCssText上;這裡如果聯用selectivizr可以少次ajax請求
        translate( sheet.styleSheet.rawCssText, href, media );
        parsedSheets[ href ] = true;
    } else {
        if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base)
            || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){
            requestQueue.push( {
                href: href,
                media: media
            } );
        }
    }
}
.......

其餘的程式碼就是ajax實現和translate media query的max-width min-width的邏輯了;可以看出這裡必須依賴ajax請求css路徑才能得到css檔案中的mediaquery的內容,那ajax的跨域問題就要解決了;由於我們的靜態資源都是要放cdn的,respond.js也給出了跨域方法,即引入代理頁面。

//把cross-domain/respond-proxy.html 放到cdn上
//把cross-domain/respond.proxy.gif 放到當前域伺服器上
<!-- Respond.js proxy on external server -->
<link href="http://externalcdn.com/respond-proxy.html" id="respond-proxy" rel="respond-proxy" />

<!-- Respond.js redirect location on local server -->
<link href="/path/to/respond.proxy.gif" id="respond-redirect" rel="respond-redirect" />

<!-- Respond.js proxy script on local server -->
<script src="/path/to/respond.proxy.js"></script>

這裡ajax跨域實現是通過代理頁面將獲取到的css,再通過window.name通訊實現;如在respond.proxy.js中

function checkFrameName() {
    var cssText;

    try {
        cssText = iframe.contentWindow.name;
                var now = new Date().getTime(),useTime = now - initTime;
        alert('獲取css耗時:'+ useTime + 'ms');
    }
    catch (e) { }

    if (cssText) {
        ……//銷燬之前用於通訊的iframe,後續回撥callback
        callback(cssText);
    }
    else{
        win.setTimeout(checkFrameName, 100);
    }
}
win.setTimeout(checkFrameName, 500);//500ms後確認內部iframe的name值是否傳遞過來,後續再更新當前viewport該用的css。

因為實現跨域代理的問題,初始化頁面時應用上全部css耗時較長,以下光測試從開始執行該js檔案到css取回呼叫之前的耗時為500ms-515ms之間(每次重新整理結果不一樣),ie8下測試結果如下

測試結果發現,重新整理頁面後會有明顯的閃屏(以該測試demo為例,一開始頁面背景是黑色的,這是預設css中的,跨域js執行完成後分析出media query中的該viewport尺寸下應該應用red的背景,所以又變成紅色),間隔時間為500ms以上。所以體驗不是很好,而且該場景中ajax跨域目前已經沒有更好的實現方式,500ms間隔的閃屏避免不了。

同時因為是ajax請求css,所以會因為響應式而額外產生一個請求,好在之前css請求過一遍,這次ajax請求是讀取瀏覽器快取中的,如下圖中fiddler的檢測結果中的第三個請求和第六個請求:

  • 優點:壓縮後僅1k,不跨域時效能ok,只需引入respond.js通用易用
  • 缺點:僅支援media query的min-width和max-width(用於響應式夠用);支援跨域,雖然配置有點麻煩,實現跨域代價高而且有閃屏體驗欠佳。

css3-mediaqueries-js 官方文件和demo都沒有,相對於respond.js css3-mediaqueries-js支援幾乎所有的media query的語法,訪問 測試demo

其實現邏輯和respond.js差不多,只是更加支援的media query更加全面,同時支援內聯style,支援各種寬度單位(em|ex|px|in|cm|mm|pt|pc),但是這裡的初始化是在domready後執行,為了讓使用者感覺不出頁面有閃屏(之前應用初始化樣式然後js提取media query中的樣式再覆蓋一遍)現象,這裡的實現是先將html移出可視區域外,等解析完media query後再重置回來,但實際目測感覺稍有閃屏(當然這裡的測試是測試body背景色,移出可視區域外不管用,當然絕大部分響應式場景是適用的),實現如下:

// prevent jumping of layout by hiding everything before painting <body> 先將html移出可視區域外
var docEl = document.documentElement;
    docEl.style.marginLeft = '-32767px';

// make sure it comes back after a while 異常處理,萬一獲取mediaquery css失敗,重置回來
setTimeout(function () {
  docEl.style.marginTop = '';
}, 20000);

……

// return visibility after media queries are tested 生效後重新可見
cssHelper.addListener('cssMediaQueriesTested', function () {
    // force repaint in IE by changing width
    if (ua.ie) {
        docEl.style.width = '1px';
    }
    setTimeout(function () {
        docEl.style.width = ''; // undo width
        docEl.style.marginLeft = ''; // undo hide
        var now = new Date().getTime();
        var useTime = now - initTime;
        alert('media query生效時間:'+useTime+'ms');
    }, 0);
    // remove this listener to prevent following execution
    cssHelper.removeListener('cssMediaQueriesTested', arguments.callee);
});

其餘實現和respond.js基本一致,也需要使用ajax,所以css3-media-queries.js本身不支援跨域,當然非要支援跨域也可以,也可以像respond.js一樣使用代理頁面跨域即可,但也會出現閃屏的現象。還是先看看不跨域情況下,大多數人為什麼選擇respond.js,主要原因還是完美支援的media query特性導致壓縮後16K,下載和執行時間都遜於respond.js,下面是同域下在ie8的測試結果(耗時140ms而respond.js僅15ms):

  • 優點:1、基本支援所有css3中的media query語法
  • 缺點:1、不支援跨域(如cdn),就算支援了跨域也存在閃屏現象;2、和respond.js對比效能較差

因為css/js需要放到cdn上面,需要跨域,css3-mediaqueries-js不支援跨域,respond.js支援跨域但是實現跨域後效能較差,有閃屏體驗也差,而且配置麻煩,不方便各個業務通用。對比respond.js和css3-mediaqueries-js可知,實現響應式應用min-width和max-width足矣;同時模擬media query的效果只需要在2個關鍵時間點根據viewport切換css(初始化頁面時和window.resize)即可。所以可以選擇切換css link,可以動態切換css塊,也可以切換class

  • 切換css link(優點:邏輯清晰;缺點:增加請求數,維護麻煩,如修改一個模組涉及到3個尺寸的響應,至少需要改3個檔案)
<link rel="stylesheet" type="text/css" media="screen and (max-width: 990px)" href="respond750.css&uuot;>
<link rel="stylesheet" type="text/css" media="screen and (max-width: 1200px)" href="respond990.css">
  • 切換內聯css塊(respond.js和css3-mediaqueries-js就是通過js分析出media query然後自動根據當前viewport切換css塊,這個理想環境下是最好的,自動分析只管寫media query,但是依賴ajax獲取css內容,跨域實現成本高體驗也不好)
  • 全域性切換class(特別是初始化頁面時最好在頁面內容未開始渲染之前切換class,不然會出現像 韓國naver購物頻道 在寬屏時重新整理效果,重新整理時內容由中間向外偏),特定viewport用特殊全域性class標記,響應式樣式繼承在該class下,實現大致如下: