Share
Embed
Polyfilling The HTML5 Gaps With JavaScript
Note: (Better) HTML version of these slides are available are at http://addyosmani.com/blog/polyfilling-the-html5-gaps/
We all want to use the hot new features being implemented in modern browsers right away, but there's a small problem holding us back: how can we use these great new capabilities whilst ensuring older browsers still render pages and features as expected? Polyfills are here to help.
Polyfills are JavaScript shims that replicate the standard API found in native features of new browsers for those without such features and in this talk I'll take you through how to both use specially written JavaScript to emulate HTML5 features and even pick up pro-tips about writing your own polyfills along the way.
Addy Osmani
December 14, 2011
Transcript
107 slides
-
A little about me @addyosmani JavaScript & UI Developer @
AOL jQuery [Bug Triage/Docs/Learning] teams Written polyfills for Page Visibility API, WebKit speech input and more. I ! making the web better " " " " " -
WebSocket API support with instant fallbacks to long polling using
socket.io (Demo) -
This is only the tip of the iceberg Imagine if
we could polyfill things like.. In-browser speech detection (WebKit/Chrome) CSS Shaders (Chrome Canary) or even... ! ! ! -
All of these features are now possible.. Media queries WebSockets
Web Storage HTML5 video Web Workers Application cache WebGL and more!! but support is fragmented ! ! ! ! ! ! ! ! -
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. Media queries - IE9, 10 WebSockets - IE10 HTML5 video - IE9, 10 Web Workers - IE10 HTML5 History API - IE10 Application cache - IE10 WebGL - Unknown ! ! ! ! ! ! ! -
Hold on! Support isn't just an IE issue! Bleeding-edge features
aren't available in all modern browsers at the same time. CSS Regions CSS Grid Layout CSS Filter Effects Web Audio API Page Visibility API ContextMenu API Download API ! ! ! ! ! ! ! -
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 Use native capabilities if a browser does support a feature Use fallbacks (Java applets, Flash, JS) for browsers that don't Provide as consistent an experience as is possible* *More on this later! ! ! ! ! -
What polyfills are widely used today? HTML5Shim/HTML5Shiv respond.js mediaelement.js socket.io
flashcanvas store.js / AmplifyJS CSS3Pie history.js/pjax ! ! ! ! ! ! ! ! -
Modernizr - feature detection made simple Adds classes to the
html tag based on browser capabilities <html class="no-cssgradients boxshadow no-cssanimations ..."> This enables styling rules that can only be rendered in capable browsers .no-cssgradients .shinybutton { background: url("shinybutton.jpg"); } .cssgradients .shinybutton { background-image: linear-gradient(top, #555, #333); } ! ! -
Modernizr feature-detection tests Tests for many features come readily built-in
(hot) Does the browser support WebGL? if (Modernizr.webgl){ loadAllWebGLScripts(); // webgl assets can easily be > 300k } else { var msg = 'WebGL is not available in your browser'; document.getElementById( '#notice' ).innerHTML = msg; } We'll look at more Modernizr feature detection tests later. ! ! -
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 All browsers have a master list of elements supported If an element isn't on this list it's considered unknown How should these elements be styled? ! ! ! -
More issues oldIE doesn't allow these elements to be styled
at all What does the DOM for an unknown element look like? 1. IE inserts it as a empty node without any children 2. Children of the element are treated as siblings ! ! ! ! ! -
The solution Create a dummy element in the page before
using it Don't even need to insert it into the DOM! <!DOCTYPE html> <html> <head> <style>article { border:1px solid red; display:block}</style> <script>document.createElement('article');</script> </head> <body> <article> <p>Hello FITC!</p> </article> ... ! ! -
So, HTML5Shim.. Runs a little loop in JavaScript to 'enable'
all the different elements in HTML5 Allows us to style tags like <section> instead of <div> in older browsers <section> <p>I can now be styled in all browsers!</p> </section> Remember, polyfills still needed for other features ! ! -
It's important to remember.. Just because you can load a
polyfill doesn't mean you should The fastest loading page on the planet is this Each thing added to this page means more time the browser has to spend loading or processing Consider performance - developers don't benchmark polyfills enough (more on this soon!) Look at speed, value added, loading mechanism ! ! ! ! ! -
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 (standalone, Modernizr) - Alex Sexton LabJS - Kyle Simpson YeahNo.js - also by Kyle ! ! ! -
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 Why should you give writing polyfills a go?
Use modern/bleeding-edge features in today's browsers Fantastic opportunity to learn subtleties in vendor implementations It'll force you to read specifications and understand them If it's reliable, other developers may also be able to use it ! ! ! ! -
Specifications - What does the API look Like? Check standards
groups like the W3C for specs -
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 Objects like window and document are your friend Fire up the console and look at this: window //explore me! BOOM. Often the first place to check for support of new features Careful. Not all browsers have the same naming conventions for experimental features ! ! ! ! -
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 it's
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 -
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 Simple CSS property support testing -
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 -
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 -
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 Going further: snippets from has.js -
Going further: snippets from has.js HTML5 audio feature test for
WAV var audio = document.createElement('audio'); audio.canPlayType("audio/wav; codecs=1"); //probably CSS3 support detection (border radius) var elStyle = document.createElement('div').style; if(typeof elStyle['borderRadius']=="string"){ //supported } Modernizr snippets -
Modernizr snippets File API: Where a spec includes multiple features
!!(window.File && window.FileList && window.FileReader) Data URI Support var dataURI = new Image(); datauri.onload = function(){ return (datauri.width == 1 && datauri.height == 1); // true or false } datauri.src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; -
Asynchronous Example: WebP (lossless image format) (function(){ // full version:
http://bit.ly/qLJbFj var image = new Image(); image.onerror = function() { Modernizr.addTest('webp', false); }; image.onload = function() { Modernizr.addTest('webp', function() { return image.width == 1; }); }; image.src = 'data:image/webp;base64,UklGRj..'; }()); -
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. -
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 -
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! }); Please enter some text -
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 This was a quick example and the
code didn't account for password inputs, textareas or values of 'placeholder' entered It will also submit the placeholder values if no value is entered. Ideally use this awesome plugin by @mathias instead ! ! ! -
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? -
Performance testing involved.. Discovering how well the native feature performed
Benchmarking how well various polyfills for it performed Deciding which browsers to use it in based on this information ! ! ! -
Distribution check-list Before you release your polyfill into the wild..
Unit tests - do your best to include them if possible Documentation - what does and doesn't it do? Support - level of future-support and maintenance Alternatives - acknowledge, describe differences. help users (Optional, but nice) Performance tests ! ! ! ! ! -
Will they be around forever? As browser vendors implement new
specs and features, the need for specific polyfills will decrease. There will be a day when oldIE is dead but polyfills will still come in useful for filling missing gaps in other browsers Until then, regularly test latest browsers in between releases to ensure the polyfill isn't loading unless needed ! ! -
SUMMARY What did we learn today? Polyfills are amazing when
used responsibly Writing your own polyfills or feature detection tests isn't as hard as it looks Don't be afraid to give users a lower-res experience where it makes sense Benchmark what you do to ensure users are given the fastest experience possible ! ! ! ! -
That's a wrap! For more on me: @addyosmani http://addyosmani.com http://github.com/addyosmani
G+:Addy Osmani Big thanks to @paul_irish, @mathias, @peol, @rem and others for their previous work in this area and technical reviews. ! ! ! !