Related links and resources
Tweet

Polyfilling the HTML5 Gaps with Addy Osmani

A little about me

I occasionally battle the dark side.

Why Do Polyfills Matter?

First do it. Then do it right. Then do it better.
  1. Do it: Get your application or demo working using the native feature in a browser that supports it
  2. Do it right: Display a notice if the feature isn't supported in an incompatible browser
  3. Do it better: Attempt to polyfill the behaviour so that it works in all/most browsers

What's a polyfill? Let's take a look at some.

WebSocket support with instant fallbacks to long polling using socket.io

<canvas> in IE7 & 8 with FlashCanvas (Demo)

getUserMedia() (Canary / Opera Labs) (Demo)

My getUserMedia() shim (cross-browser) (Demo)

WebGL in IE7, 8 & 9 using JebGL (Video)

CSS3Pie - A polyfill for CSS3 decoration features

CSS3 border-radius,box-shadow and gradients in IE7

CSS3 patterns gallery for modern browsers (Demo)

Which we can polyfill for IE9 (Demo)

This is only the tip of the iceberg

Imagine if we could polyfill things like..

Features & Support

All of these features are now possible..

but support is fragmented

HTML5 & CSS3 Readiness (View)

Can I Use? (View)

oldIE: Internet Explorer 6, 7 & 8. aka, the three browsers often getting the low-res experience.
Paul Irish

Our biggest issue - browser support

New features will never be available in oldIE.

Unless you're using something like
Chrome Frame

Google Chrome Frame

Hold on! Support isn't just an IE issue!

Bleeding-edge features aren't available in all modern browsers at the same time.

Can we use modern features now? Let's wait...or

Use Polyfills & Shims

Polyfills are a type of shim that retrofit legacy browsers with modern HTML5/CSS3 features
Remy Sharp
Shims refer to any code that intercepts API calls and provides a layer of abstraction
Paul Irish

What can they do?

Polyfills help us use today's modern features in yesterday's browsers

What polyfills are widely used today?

HTML5 Please - Use features responsibly (View)

HTML5 Please API (View)

HTML5 Please Widget - Is your browser compatible?

Modernizr - feature detection made simple

Modernizr feature-detection tests

We'll look at more Modernizr feature detection tests later.

READ THE DOCS FOR MORE ON BUILT-IN TESTS

Modernizr also supports custom feature detection plug-ins

WebAudio API

Modernizr.addTest('webaudio', 
        !!(window.webkitAudioContext || window.AudioContext));

Track API

Modernizr.addTest('trackapi', 
        typeof document.createElement('video').addTextTrack 
        === 'function');

Include HTML5Shim & we can also enable styling and using semantic HTML5 elements in oldIE

More issues

The solution

<!DOCTYPE html>
<html>
<head>
<style>section { border:1px solid red; display:block}</style>
<script>document.createElement('section');</script>
</head>
<body>
  <section>
    <p>Hello FITC!</p>
  </section>
...

We can now style a HTML5 element in IE6 and above

So, HTML5Shim..

Issues & Solutions

It's important to remember..

Take the 'hardboiled' approach

'Hardboiled Web Design' by Andy Clarke

It's okay for users to get different experiences

Lo-res - users are still delivered content. Default stylesheet used instead.

Be a champion of performance

Optimize to make best use of the capabilities a user's browser supports

Only load polyfills if they're absolutely needed

There are some great script loaders that can help with conditional loading

yepnope.js - an asynchronous conditional resource loader

Example: conditionally load a geolocation polyfill and stylesheet

yepnope({
  test: Modernizr.geolocation,
  yep: 'regular-styles.css',
  nope: ['modified-styles.css', 'geolocation-polyfill.js'],
  callback: function (url, result, key) {
    if (url === 'modified-styles.css') {
      alert("woohoo! it's loaded");
    }
  }
});
        

Modernizr includes yepnope.js in special builds

Supports similarly loading up a geolocation polyfill depending on support

Modernizr.load({
  test: Modernizr.geolocation,
  yep : 'geo.js',
  nope: 'geo-polyfill.js'
});
        

LabJS

Example: conditionally load a JSON polyfill if it isn't natively supported

 $LAB.script(function(){
   if (typeof JSON == "undefined") return "json2.js";
})
.wait()
.script("myotherscript.js");       
      

An alternative:

$LAB.script(typeof JSON == "undefined" ? "json2.js" : false).wait()
.script("myotherscript.js");
        

YeahNo.js - a yepnope API wrapper around LabJS

Example: conditionally load a geolocation polyfill and stylesheet

yepnope({
  test: Modernizr.geolocation,
  yep: 'regular-styles.css',
  nope: ['modified-styles.css', 'geolocation-polyfill.js'],
  callback: function (url, result, key) {
    if (url === 'modified-styles.css') {
      alert('The Styles loaded!');
    }
  }
});
        

Writing Polyfills

Writing Polyfills

Why should you give writing polyfills a go?

Test what features your current browser supports (View)

Does Browser-x natively support the feature? Browser.next?

Specifications - What does the API look Like? Check standards groups like the W3C for specs

Quirks - What quirks do older browser implementations suffer from? QuirksMode.org

You can't detect 'HTML5 Support', but you can detect support for individual features
Mark Pilgrim

Feature detection has some friends

There are some useful tips to keep in mind

Testing techniques in order of preference:

  1. Feature testing - check for the existence of a method and if it returns the correct output
  2. Feature detection - check for the existence of a method
  3. Weak inference - check for the existence of an unrelated method
  4. UA sniffing - check the browser's user agent string

 

Thanks to @mathias and @jdalton for this list

More on these very soon!

Support

Detecting finalized and unfinalized features. Test if:

  1. Property of the feature exists on a global object (window or navigator)
  2. Property of the feature exists on a specific element
  3. Method of the feature exists on a specific element where the value it returns can be tested
  4. Property of the feature can be set and its value retained

1. Property exists on a global object

Testing for geolocation support

function isGeolocationSupported(){
  return !!navigator.geolocation;
}

and with Modernizr it's as simple as..

if(Modernizr.geolocation){
  // supported
}else{
  // not supported
}

2. Property of the feature exists on a specific element

Testing for <canvas> support

function isCanvasSupported(){
  return !!document.createElement('canvas').getContext;
}

and with Modernizr its..

if(Modernizr.canvas){
  // supported
}else{
  // not supported
}

3. Method of the feature exists on a specific element where the value it returns can be tested

Testing for <audio> support

function isAudioSupported(){
  return !!document.createElement('audio').canPlayType;
}

and with Modernizr its..

if(Modernizr.audio){
  // supported
}else{
  // not supported
}

But we can take this further

Testing for <audio> format support

if(isAudioSupported()){
  
  var audio = document.createElement('audio');
  if(audio.canPlayType('audio/mpeg')=='probably'){
    //supports MP3 audio
    audio.src = 'music.mp3';
  }
  else if(audio.canPlayType('video/ogg; codecs="theora"')=='probably'){
    //supports Ogg/Vorbis audio
    audio.src = 'music.ogg';
  }
}else{
  //load a flash fallback
}

and with Modernizr its..

if(Modernizr.audio){
  var audio = new Audio();

  //If Ogg is supported, load 'music.ogg'
  //Otherwise the MP3 or M4A fallback depending
  //on browser support
  audio.src = Modernizr.audio.ogg ? 'music.ogg' :
              Modernizr.audio.mp3 ? 'music.mp3' :
                                    'music.m4a';
}else{
  //use a flash fallback
}

4. Property of the feature can be set and its value retained

Testing for <input type="color"> support

function isColorPickerSupported(){
  var input = document.createElement('input');
  input.setAttribute('type','color');
  return input.type !== 'text';
}

and with Modernizr its..

if(Modernizr.inputtypes.color){
  // supported
}else{
  // not supported
}

Feature-Detection Helpers

Very, very simple JavaScript API support testing

function isAPISupported(api, source){
  //return (api in source) or..
  return !!source[api];
};

// testing with google chrome
isAPISupported('geolocation', navigator); // true
isAPISupported('pushState', history); // true
isAPISupported('localStorage', window); // true
isAPISupported('sessionStorage', window); // true

Note: If a third-party library is extending host objects, 'a' in b or b.a testing may provide unreliable results. Keep in mind other scripts on the page!

Simple CSS property support testing

function isPropSupported(prop){
  var el = document.createElement('div');
  return prop in el.style;
};

isPropSupported('borderRadius'); // true
isPropSupported('boxShadow'); // true
isPropSupported('textShadow'); // true

Simple CSS selector support testing

function isSelectorSupported(sel){
    var el = document.createElement('div');
    el.innerHTML = '­<style>'+ sel + '{}</style>';
    document.body.appendChild(el);
    return !!el.lastChild.sheet.cssRules[0];
};

isSelectorSupported('::first-child'); // true
isSelectorSupported('::after'); // true
isSelectorSupported('::before'); // true
isSelectorSupported(':nth-child(even)'); // true

Basic HTML Attribute support testing

function isAttribSupported(prop, el){
  var el = document.createElement(el);
  return prop in el;
}

// Some simple HTML5 feature-detection tests

isAttribSupported('placeholder', 'input'); // true
isAttribSupported('play', 'video'); // true
isAttribSupported('pause', 'audio'); // true
isAttribSupported('getContext', 'canvas'); // true

Vendor prefixes

Allow vendors to implement experimental features before they've been finalized

// From css3please.com:
.box_transition {
  -webkit-transition: all 0.3s ease-out;  /* Saf3.2+, Chrome */
     -moz-transition: all 0.3s ease-out;  /* FF4+ */
      -ms-transition: all 0.3s ease-out;  /* IE10? */
       -o-transition: all 0.3s ease-out;  /* Opera 10.5+ */
          transition: all 0.3s ease-out;  /*fast-forward compatible*/
}
        

Edge-features occasionally need to be tested prepending a vendor prefix to the feature name.

CSS3 Please - The Cross-Browser Rule Generator (View)

Getting the vendor prefix

function getPrefix(prop){
  var prefixes = ['Moz','Khtml','Webkit','O','ms'],
      elem     = document.createElement('div'),
      upper    = prop.charAt(0).toUpperCase() + prop.slice(1);

  if (prop in elem.style)
    return prop;
        
  for (var len = prefixes.length; len--; ){
    if ((prefixes[len] + upper)  in elem.style)
      return (prefixes[len] + upper);
  }
  
  return false;
}

console.log(getPrefix('transform'));//WebkitTransform

Getting the vendor prefix with Modernizr

console.log(Modernizr.prefixed('transform'));//WebkitTransform

Polyfills with jQuery

A very very simple jQuery placeholder polyfill

The HTML5 Placeholder attribute is used as follows:

<input type="text" placeholder="Please enter some text"/>

Begin by iterating over all the input elements with a placeholder attribute

$("input[placeholder]").each(function() {
      // more logic to come!  
});

Step 2

Get the value of the placeholder attribute, remove default placeholder:

$("input[placeholder]").each(function() {
    var $e = $(this),
    placeholder = $e.attr("placeholder");

    $e.removeAttr("placeholder").val(placeholder);

    // A little more left to go
});

Step 3

Polyfill the placeholder text behavior to be cross-browser:

$("input[placeholder]").each(function() {
    var $e = $(this),
    placeholder = $e.attr("placeholder");

    $e.removeAttr("placeholder").val(placeholder);

    $e.bind("focus blur", function(e) {
    if (e.type === "focus" && $e.val() === placeholder) { $e.val(""); }
      else { if (!$e.val()) { $e.val(placeholder); } }
    });
});

Some quick notes

Watch out! Not everything can be detected (View)

Performance

  • Benchmark performance of the complete polyfill
  • Are there other scripts loading on the page?
  • Polyfills relying on other polyfills?

Break your polyfill into smaller parts. Can those be tested or optimized further?

Is the polyfill visibly slow in some browsers? Use vs. lose

Benchmark performance with jsPerf.com (View)

Case study: forEach() (ES5)

Performance testing involved..

Getting the polyfill out there

Distribution check-list

Before you release your polyfill into the wild..

Offer minified versions of your polyfill

YUICompressor, UglifyJS, Closure Compiler

Host on GitHub

http://github.com

Post To The Modernizr Polyfills Wiki

The Future

Polyfills will probably still exist in the future.

Will they be around forever?

As browser vendors implement new specs and features, the need for specific polyfills will decrease.

Firefox nightlies

Chromium (Stable, Beta, Canary builds)

Opera.next

Internet Explorer Preview

SUMMARY

What did we learn today?

That's a wrap!

For more on me:

 

Big thanks to @paul_irish, @mathias, @peol, @rem and others for their previous work in this area and technical reviews.