2014-01-05

HTML5 與 CSS3 的偵測器 - Modernizr 2.7.1

自從 Firefox 與 Chrome 打破 IE 獨大後,接著智慧型手機在瀏覽器市場帶來更大的衝擊,不只是各家爭鳴,還各版橫行,加上 HTML 5 與 CSS3 帶來許多嶄新的功能,讓網頁開發變得更有挑戰,也更頭大。
「這個新功能很炫,但是可以用了嗎?瀏覽器支援了嗎?」
這是網頁開發一定會遭遇的問題,幸好 Firefox 與 Chrome 更新速度很快,而且常常(偷偷的)自動更新,比較沒有這些問題,但是 IE 就不是這麼一回事了,尤其是一堆舊的 IE 遲遲不願更新的情況,讓 IE 一直是網頁設計最大的挑戰,IE 真的為網頁設計帶來許多就業機會啊!

Modernizr 是什麼?

官方定義如下:
「Modernizr is a JavaScript library that detects HTML5 and CSS3 features in the user’s browser.」
重點如下:
  • Javascript 程式
  • 用來偵測
  • 瀏覽器是否支援 HTML5 與 CSS3 功能
Modernize 的功能如下:
  • 用來偵測使用者的瀏覽器是否支援想要使用的功能。
  • 適合用來做 Progressive Enhancement。
  • 在頁面載入時立即執行所有偵測(可以自訂偵測項目),並將偵測結果放在一個 Javascript 物件(Modernizr)供 Javascript 程式使用,再另外增加 CSS class 到 HTML 物件(html)上供 CSS 使用。
  • 目前可以測試超過 40 個功能,而且厲害的是只需要幾毫秒的時間。
  • 整合 yepnope.js 做條件式載入,有關 yepnope 請參考 yepnope.js 讓瀏覽器只載入需要的檔案 與 More in yepnope 1.5.4

功能偵測或瀏覽器偵測?

以前習慣使用瀏覽器偵測,也就是使用navigator.userAgent來識別瀏覽器,其他作法和 Modernizr 類似,可以呼叫 javascript function,也會在 DOM 上加上 class 識別。
nc.isIE = function() {
  var agent = navigator.userAgent.toLowerCase();
  // isIE6 - /msie 6/.test(agent);
  // isIE7 - /msie 7/.test(agent);
  // isIE8 - /msie 8/.test(agent);
  // ...
  return /msie/.test(agent);
};

nc.isFF = function() {
  var agent = navigator.userAgent.toLowerCase();
  // isFF2 - /firefox\/2/.test(agent);
  // isFF3 - /firefox\/3/.test(agent);
  return /firefox/.test(agent);
};

nc.isChrome = function() {
  var agent = navigator.userAgent.toLowerCase();
  // isChrome2 - /chrome\/2/.test(agent);
  return /chrome/.test(agent);
};

nc.browserTag = function() {
  if (nc.isChrome()) {
    $('body').addClass('chrome');
  }
  else if (nc.isFF()) {
    $('body').addClass('ff');
  }
  else if (nc.isIE()) {
    $('body').addClass('ie');
  }
};

$(function(){
  nc.browserTag();
});
但這樣有兩個缺點,先不管navigator.userAgent是可以竄改的,沒試過的瀏覽器就不會出現在名單上,例如 Mac 的瀏覽器、Opera、手機上的等,就算是已經試過的瀏覽器,一旦出了新版本,程式也要翻新才行,這樣實在太累了。

另外得知道哪個瀏覽器不支援哪個功能,這更是怎麼都做不好的工作。

功能偵測讓事情簡單多了,只要有辦法知道瀏覽器有沒有支援這樣的功能,就不用試過所有的瀏覽器與版本,費盡力氣去維護一份只會愈來愈長的瀏覽器名單,並且熟記哪個瀏覽器哪個版本不支援哪個功能,有了功能偵測這些都可以丟掉了。

以 IE8 以下不支援Array.indexOf為例,功能偵測只要判斷瀏覽器是否支援indexOf,然後就可以補強,而不用管是哪個瀏覽器這麼弱。
if (!Array.prototype.indexOf) {
 Array.prototype.indexOf = function(obj, start) {
  for (var i = (start || 0), j = this.length; i < j; i++) {
   if (this[i] === obj) {
    return i;
   }
  }
  return -1;
 };
}
以前雖然聽過功能偵測,但不知道要怎麼偵測,所以一直停在瀏覽器偵測的階段。

Modernizr 如何做偵測?

說穿了很簡單,就是直接建立想要偵測的 HTML5 DOM 物件,或者在一個 DOM 物件加上想要偵測的 CSS3 style,然後立即取回,沒有支援的瀏覽器只會回傳空值或者undefined

Modernizr 支援的瀏覽器

電腦上的瀏覽器:
  • IE6+
  • Firefox 3.5+
  • Opera 9.6+
  • Safari 2+
  • Chrome
行動裝置上的瀏覽器:
  • iOS 的 Safari
  • Android 的 WebKit
  • Opera Mobile
  • Firefox Mobile
  • 以及仍在測試中的 Blackberry 6+

安裝 Modernizr

安裝?Javascript library 不都是用script加進去就好嗎?是這樣沒錯,但位置很重要。

標準的作法是將script放在head裡,但後來的發展,導致script可能出現在html裡的很多地方。

如果有使用讓不支援 html5 的瀏覽器支援 html5 的 html5shiv 的話,html5shiv 一定要放在body之前,而 Modernizr 則一定要在 html5shiv 之後。

如果有用到 Modernizr 為html加上的 CSS class 的話,那 Modernizr 一定要放在head裡,否則會出現畫面跳動,從沒有 style 跳到套用 style。
<!DOCTYPE html>
<html>
<head>
<meta charset="BIG5">
<title>RWD</title>
<style type="text/css">
.m {
  float: left;
  border: 1px solid #aaa;
  background-color: #eee;
  padding: 2px;
  margin: 2px;
}
</style>
<script src="jquery-1.10.2.min.js"></script>
<script src="modernizr-dev-2.7.1.js"></script>
<script>
  $(function() {
    var r = '';
    for ( var k in Modernizr) {
      r += '<div class="m">' + k + '=' + Modernizr[k] + '</div>';
    }
    $('#ModernizrJS').html(r);
  });
</script>
</head>
<body>
  <div id="ModernizrJS"></div>
</body>
</html>
在 Chrome 31.0.1650.63 版可以看到 Modernizr 為html加了許多 class。


以及 Javascript 物件Modernizr裡有哪些屬性。

Polyfill

最近一直看到這個字典查不到的字,Wikipedia 的解釋:
「In web development, a polyfill (or polyfiller) is downloadable code which provides facilities that are not built into a web browser. For example, many features of HTML5 are not supported by versions of Internet Explorer older than version 8 or 9, but can be used by web pages if those pages install a polyfill.[1] Web shims[2] and HTML5 Shivs are a related concepts.」
歸納的重點如下:
  • Javascript library
  • 為舊版的瀏覽器
  • 提供新版瀏覽器才有的功能
  • 新功能多半是指 HTML5
  • 目前常見的有 Webshims 與 html5shiv
Polyfill 簡單講就是補強,而 Modernizr 不是 polyfill,它只是偵測,若有需要可以搭配其他 polyfill 來補強瀏覽器尚未提供的功能。

HTML5 Cross Browser Polyfills 就是一個補強的好選擇,它幾乎提供所有 Modernizr 能偵測功能的補強。

Modernizr 提到一個有趣的觀點,有 polyfill 並不代表一定就要用它,不需要用一堆 script 把 IE7 搞的像 Chrome 一樣,除了工程師,沒有使用者會同時開兩個瀏覽器來看同一個網站,所以讓 IE7 維持它原本應有的面貌,事情會比較輕鬆也比較不會讓使用者感到困惑。

Modernizr.load()

偵測完要做什麼?當然是執行特定的程式,如果瀏覽器支援某功能那就執行 A 程式,不支援的話就執行 B 程式,但是如果都寫在一起,就有一點浪費頻寬下載一部分用不到的程式和瀏覽器執行效能,因為永遠只會在 A 程式與 B 程式之間擇一執行,所以 Modernizr 整合了條件式載入 yepnope.js
Modernizr.load({
  test: Modernizr.geolocation,
  yep : 'geo.js',
  nope: 'geo-polyfill.js'
});
有關 yepnope.js 的使用,請參考 yepnope.js 讓瀏覽器只載入需要的檔案 與 More in yepnope 1.5.4

Modernizr 偵測的 CSS 功能

箭頭左邊是功能名稱,箭頭右邊是 Modernizr 的 Javascript 物件屬性名稱或 CSS class 名稱。

@font-face->fontface

background-size->backgroundsize

border-image->borderimage

border-radius->borderradius

box-shadow->boxshadow

Flexible Box Model->flexbox 

hsla()->hsla

Multiple backgrounds->multiplebgs

opacity->opacity

rgba()->rgba

text-shadow->textshadow

CSS Animations->cssanimations

CSS Columns->csscolumns

Generated Content (:before/:after)->generatedcontent

CSS Gradients->cssgradients

CSS Reflections->cssreflections

CSS 2D Transforms->csstransforms

CSS 3D Transforms->csstransforms3d

CSS Transitions->csstransitions

這不是我來 Modernizr 的目的,看過就好。

Modernizr 偵測的 HTML 功能

applicationCache->applicationcache

Canvas->canvas

Canvas Text->canvastext

Drag and Drop->draganddrop

hashchange Event->hashchange

History Management->history

HTML5 Audio->audio

HTML5 Video->video

IndexedDB->indexeddb

Input Attributes->autocomplete, autofocus, list, placeholder, max, min, multiple, pattern, required, step

Input Types->search, tel, url, email, datetime, date, month, week, time, datetime-local, number, range, color

localStorage->localstorage

Cross-window Messaging->postmessage

sessionStorage->sessionstorage

Web Sockets->websockets

Web SQL Database->websqldatabase

Web Workers->webworkers

Modernizr 偵測的其他功能

Geolocation API->geolocation

Inline SVG->inlinesvg

SMIL->smil

SVG->svg

SVG Clip paths->svgclippaths

ouch Events->touch

WebGL->webgl

還沒找到想要的偵測?

Github 下載整包 Modernizr(右下角的 Download ZIP),解開後可以找到 feature-detects 目錄,裡面有多到讓人頭暈眼花的 plugin。

Modernizr API

事實上前面的 Modernizr.load() 就是最常用的 Modernizr API,除了特殊狀況,很少會用到以下的 API。

Modernizr.prefixed()

Modernizr.mq()

這才是我找到 Modernizr 的原因,讓 Javascript 可以執行 CSS 的 Media Query。

Media Query 可以讓網頁依據不同的大小或者用途而套用不同的 CSS,詳細用法可以參考 讓 IE8 認識 CSS Media Query 的 Respond.js,但這種方式有一個小小的缺點,由於是寫在同一個 CSS 檔案裡,也就會下載所有 Media Query 的 CSS,即使沒用到,多半也用不到,除非使用者瀏覽到一半突然改變瀏覽器大小,但這是很少發生的情況,除了網頁設計師。

因此如果可以依據 Media Query 來下載相關的檔案,那就可以解決這問題了。

Modernizr.mq() 有一些限制:
  • 是針對當下的瀏覽器狀況做偵測,也就是說當稍後使用者自行改變瀏覽器大小或旋轉手機方向以致 orientation 改變時,得重新呼叫 Modernizr.mq() 做偵測。
  • 一定要指定 media type,可以用all
$(window).resize(function() {
  Modernizr.load({
    test : Modernizr.mq('only all and (max-width: 400px)'),
    yep : 'yep.css',
    nope : 'nope.css'
  });
}).resize();
  • 如果遇到不支援 Media Query 的瀏覽器,那不管 mq 的內容為何,永遠得到 false,不過目前已經有辦法 讓 IE8 認識 CSS Media Query 的 Respond.js。
// 可以用來偵測是否支援 Media Query
Modernizr.load({
  test : Modernizr.mq('only all'),
  yep : 'yep.css',
  nope : 'nope.css'
});

Modernizr.addTest()

如果 Modernizr 內建的測試與 Github 的 featrue-detects 都找不到想要的測試,那就自己新增吧。

有四種 addTest() API 可以使用。
Modernizr.addTest(str, fn)
Modernizr.addTest(str, bool)
Modernizr.addTest({str: fn, str2: fn2})
Modernizr.addTest({str: bool, str2: fn})
官方範例如下。
Modernizr.addTest('track', function(){
  var video = document.createElement('video');
  return typeof video.addTextTrack === 'function'
});
支援 track 的瀏覽器會得到.track的 class 與Modernizr.track為 true 的結果,不支援 track 的瀏覽器則會得到.no-track的 class 與Modernizr.track為 false 的結果。

不建議在測試名稱裡使用 dash(-),也就是上面的 track。

Modernizr.testStyles()

看不懂。

Modernizr.testProp()

測試是否支援指定的 CSS style property,property 名稱必須使用駝峰式。
Modernizr.testProp(str);
官方範例如下。
Modernizr.testProp('pointerEvents'); // true or false

Modernizr.testAllProps()

Modernizr.testProp(),除了指定的 property,還會測試加上瀏覽器 prefix 的 property。
Modernizr.testAllProps(str);
官方範例如下。
Modernizr.testAllProps('boxSizing'); // true or false

Modernizr.hasEvent()

測試指定的元件(elem)是否支援指定的事件(str)。
Modernizr.hasEvent(str [,elem])
官方範例如下。
Modernizr.hasEvent('gesturestart', elem); // true or false
---
---
---

沒有留言:

張貼留言