1/* 2 * respond.js - A small and fast polyfill for min/max-width CSS3 Media Queries 3 * Copyright 2011, Scott Jehl, scottjehl.com 4 * Dual licensed under the MIT or GPL Version 2 licenses. 5 * Usage: Check out the readme file or github.com/scottjehl/respond 6*/ 7(function( win, mqSupported ){ 8 //exposed namespace 9 win.respond = {}; 10 11 //define update even in native-mq-supporting browsers, to avoid errors 12 respond.update = function(){}; 13 14 //expose media query support flag for external use 15 respond.mediaQueriesSupported = mqSupported; 16 17 //if media queries are supported, exit here 18 if( mqSupported ){ return; } 19 20 //define vars 21 var doc = win.document, 22 docElem = doc.documentElement, 23 mediastyles = [], 24 rules = [], 25 appendedEls = [], 26 parsedSheets = {}, 27 resizeThrottle = 30, 28 head = doc.getElementsByTagName( "head" )[0] || docElem, 29 links = head.getElementsByTagName( "link" ), 30 requestQueue = [], 31 32 //loop stylesheets, send text content to translate 33 ripCSS = function(){ 34 var sheets = links, 35 sl = sheets.length; 36 37 for( var i = 0; i < sl; i++ ){ 38 var sheet = sheets[ i ], 39 href = sheet.href, 40 media = sheet.media, 41 isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 42 43 //only links plz and prevent re-parsing 44 if( !!href && isCSS && !parsedSheets[ href ] ){ 45 if( !/^([a-zA-Z]+?:(\/\/)?(www\.)?)/.test( href ) 46 || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ 47 requestQueue.push( { 48 href: href, 49 media: media 50 } ); 51 } 52 else{ 53 parsedSheets[ href ] = true; 54 } 55 } 56 } 57 makeRequests(); 58 59 }, 60 61 //recurse through request queue, get css text 62 makeRequests = function(){ 63 if( requestQueue.length ){ 64 var thisRequest = requestQueue.shift(); 65 66 ajax( thisRequest.href, function( styles ){ 67 translate( styles, thisRequest.href, thisRequest.media ); 68 parsedSheets[ thisRequest.href ] = true; 69 makeRequests(); 70 } ); 71 } 72 }, 73 74 //find media blocks in css text, convert to style blocks 75 translate = function( styles, href, media ){ 76 var qs = styles.match( /@media ([^\{]+)\{((?!@media)[\s\S])*(?=\}[\s]*\/\*\/mediaquery\*\/)/gmi ), 77 ql = qs && qs.length || 0, 78 //try to get CSS path 79 href = href.substring( 0, href.lastIndexOf( "/" )), 80 repUrls = function( css ){ 81 return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); 82 }, 83 useMedia = !ql && media; 84 85 //if path exists, tack on trailing slash 86 if( href.length ){ href += "/"; } 87 88 //if no internal queries exist, but media attr does, use that 89 //note: this currently lacks support for situations where a media attr is specified on a link AND 90 //its associated stylesheet has internal CSS media queries. 91 //In those cases, the media attribute will currently be ignored. 92 if( useMedia ){ 93 ql = 1; 94 } 95 96 97 for( var i = 0; i < ql; i++ ){ 98 var fullq; 99 100 //media attr 101 if( useMedia ){ 102 fullq = media; 103 rules.push( repUrls( styles ) ); 104 } 105 //parse for styles 106 else{ 107 fullq = qs[ i ].match( /@media ([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1; 108 rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); 109 } 110 111 var eachq = fullq.split( "," ), 112 eql = eachq.length; 113 114 for( var j = 0; j < eql; j++ ){ 115 var thisq = eachq[ j ]; 116 mediastyles.push( { 117 media : thisq.match( /(only\s+)?([a-zA-Z]+)(\sand)?/ ) && RegExp.$2, 118 rules : rules.length - 1, 119 minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9]+)px[\s]*\)/ ) && parseFloat( RegExp.$1 ), 120 maxw : thisq.match( /\(max\-width:[\s]*([\s]*[0-9]+)px[\s]*\)/ ) && parseFloat( RegExp.$1 ) 121 } ); 122 } 123 } 124 125 applyMedia(); 126 }, 127 128 lastCall, 129 130 resizeDefer, 131 132 //enable/disable styles 133 applyMedia = function( fromResize ){ 134 var name = "clientWidth", 135 docElemProp = docElem[ name ], 136 currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, 137 styleBlocks = {}, 138 dFrag = doc.createDocumentFragment(), 139 lastLink = links[ links.length-1 ], 140 now = (new Date()).getTime(); 141 142 //throttle resize calls 143 if( fromResize && lastCall && now - lastCall < resizeThrottle ){ 144 clearTimeout( resizeDefer ); 145 resizeDefer = setTimeout( applyMedia, resizeThrottle ); 146 return; 147 } 148 else { 149 lastCall = now; 150 } 151 152 for( var i in mediastyles ){ 153 var thisstyle = mediastyles[ i ]; 154 if( !thisstyle.minw && !thisstyle.maxw || 155 ( !thisstyle.minw || thisstyle.minw && currWidth >= thisstyle.minw ) && 156 (!thisstyle.maxw || thisstyle.maxw && currWidth <= thisstyle.maxw ) ){ 157 if( !styleBlocks[ thisstyle.media ] ){ 158 styleBlocks[ thisstyle.media ] = []; 159 } 160 styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); 161 } 162 } 163 164 //remove any existing respond style element(s) 165 for( var i in appendedEls ){ 166 if( appendedEls[ i ] && appendedEls[ i ].parentNode === head ){ 167 head.removeChild( appendedEls[ i ] ); 168 } 169 } 170 171 //inject active styles, grouped by media type 172 for( var i in styleBlocks ){ 173 var ss = doc.createElement( "style" ), 174 css = styleBlocks[ i ].join( "\n" ); 175 176 ss.type = "text/css"; 177 ss.media = i; 178 179 if ( ss.styleSheet ){ 180 ss.styleSheet.cssText = css; 181 } 182 else { 183 ss.appendChild( doc.createTextNode( css ) ); 184 } 185 dFrag.appendChild( ss ); 186 appendedEls.push( ss ); 187 } 188 189 //append to DOM at once 190 head.insertBefore( dFrag, lastLink.nextSibling ); 191 }, 192 //tweaked Ajax functions from Quirksmode 193 ajax = function( url, callback ) { 194 var req = xmlHttp(); 195 if (!req){ 196 return; 197 } 198 req.open( "GET", url, true ); 199 req.onreadystatechange = function () { 200 if ( req.readyState != 4 || req.status != 200 && req.status != 304 ){ 201 return; 202 } 203 callback( req.responseText ); 204 } 205 if ( req.readyState == 4 ){ 206 return; 207 } 208 req.send(); 209 }, 210 //define ajax obj 211 xmlHttp = (function() { 212 var xmlhttpmethod = false, 213 attempts = [ 214 function(){ return new ActiveXObject("Microsoft.XMLHTTP") }, 215 function(){ return new ActiveXObject("Msxml3.XMLHTTP") }, 216 function(){ return new ActiveXObject("Msxml2.XMLHTTP") }, 217 function(){ return new XMLHttpRequest() } 218 ], 219 al = attempts.length; 220 221 while( al-- ){ 222 try { 223 xmlhttpmethod = attempts[ al ](); 224 } 225 catch(e) { 226 continue; 227 } 228 break; 229 } 230 return function(){ 231 return xmlhttpmethod; 232 }; 233 })(); 234 235 //translate CSS 236 ripCSS(); 237 238 //expose update for re-running respond later on 239 respond.update = ripCSS; 240 241 //adjust on resize 242 function callMedia(){ 243 applyMedia( true ); 244 } 245 if( win.addEventListener ){ 246 win.addEventListener( "resize", callMedia, false ); 247 } 248 else if( win.attachEvent ){ 249 win.attachEvent( "onresize", callMedia ); 250 } 251})( 252 this, 253 (function( win ){ 254 255 //for speed, flag browsers with window.matchMedia support and IE 9 as supported 256 if( win.matchMedia ){ return true; } 257 258 var bool, 259 doc = document, 260 docElem = doc.documentElement, 261 refNode = docElem.firstElementChild || docElem.firstChild, 262 // fakeBody required for <FF4 when executed in <head> 263 fakeUsed = !doc.body, 264 fakeBody = doc.body || doc.createElement( "body" ), 265 div = doc.createElement( "div" ), 266 q = "only all"; 267 268 div.id = "mq-test-1"; 269 div.style.cssText = "position:absolute;top:-99em"; 270 fakeBody.appendChild( div ); 271 272 div.innerHTML = '_<style media="'+q+'"> #mq-test-1 { width: 9px; }</style>'; 273 if( fakeUsed ){ 274 docElem.insertBefore( fakeBody, refNode ); 275 } 276 div.removeChild( div.firstChild ); 277 bool = div.offsetWidth == 9; 278 if( fakeUsed ){ 279 docElem.removeChild( fakeBody ); 280 } 281 else{ 282 fakeBody.removeChild( div ); 283 } 284 return bool; 285 })( this ) 286);