Permalink
| /* Respond.js: min/max-width media query polyfill. (c) Scott Jehl. MIT Lic. j.mp/respondjs */ | |
| (function( w ){ | |
| "use strict"; | |
| //exposed namespace | |
| var respond = {}; | |
| w.respond = respond; | |
| //define update even in native-mq-supporting browsers, to avoid errors | |
| respond.update = function(){}; | |
| //define ajax obj | |
| var requestQueue = [], | |
| xmlHttp = (function() { | |
| var xmlhttpmethod = false; | |
| try { | |
| xmlhttpmethod = new w.XMLHttpRequest(); | |
| } | |
| catch( e ){ | |
| xmlhttpmethod = new w.ActiveXObject( "Microsoft.XMLHTTP" ); | |
| } | |
| return function(){ | |
| return xmlhttpmethod; | |
| }; | |
| })(), | |
| //tweaked Ajax functions from Quirksmode | |
| ajax = function( url, callback ) { | |
| var req = xmlHttp(); | |
| if (!req){ | |
| return; | |
| } | |
| req.open( "GET", url, true ); | |
| req.onreadystatechange = function () { | |
| if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){ | |
| return; | |
| } | |
| callback( req.responseText ); | |
| }; | |
| if ( req.readyState === 4 ){ | |
| return; | |
| } | |
| req.send( null ); | |
| }, | |
| isUnsupportedMediaQuery = function( query ) { | |
| return query.replace( respond.regex.minmaxwh, '' ).match( respond.regex.other ); | |
| }; | |
| //expose for testing | |
| respond.ajax = ajax; | |
| respond.queue = requestQueue; | |
| respond.unsupportedmq = isUnsupportedMediaQuery; | |
| respond.regex = { | |
| media: /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi, | |
| keyframes: /@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi, | |
| comments: /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi, | |
| urls: /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, | |
| findStyles: /@media *([^\{]+)\{([\S\s]+?)$/, | |
| only: /(only\s+)?([a-zA-Z]+)\s?/, | |
| minw: /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, | |
| maxw: /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/, | |
| minmaxwh: /\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi, | |
| other: /\([^\)]*\)/g | |
| }; | |
| //expose media query support flag for external use | |
| respond.mediaQueriesSupported = w.matchMedia && w.matchMedia( "only all" ) !== null && w.matchMedia( "only all" ).matches; | |
| //if media queries are supported, exit here | |
| if( respond.mediaQueriesSupported ){ | |
| return; | |
| } | |
| //define vars | |
| var doc = w.document, | |
| docElem = doc.documentElement, | |
| mediastyles = [], | |
| rules = [], | |
| appendedEls = [], | |
| parsedSheets = {}, | |
| resizeThrottle = 30, | |
| head = doc.getElementsByTagName( "head" )[0] || docElem, | |
| base = doc.getElementsByTagName( "base" )[0], | |
| links = head.getElementsByTagName( "link" ), | |
| lastCall, | |
| resizeDefer, | |
| //cached container for 1em value, populated the first time it's needed | |
| eminpx, | |
| // returns the value of 1em in pixels | |
| getEmValue = function() { | |
| var ret, | |
| div = doc.createElement('div'), | |
| body = doc.body, | |
| originalHTMLFontSize = docElem.style.fontSize, | |
| originalBodyFontSize = body && body.style.fontSize, | |
| fakeUsed = false; | |
| div.style.cssText = "position:absolute;font-size:1em;width:1em"; | |
| if( !body ){ | |
| body = fakeUsed = doc.createElement( "body" ); | |
| body.style.background = "none"; | |
| } | |
| // 1em in a media query is the value of the default font size of the browser | |
| // reset docElem and body to ensure the correct value is returned | |
| docElem.style.fontSize = "100%"; | |
| body.style.fontSize = "100%"; | |
| body.appendChild( div ); | |
| if( fakeUsed ){ | |
| docElem.insertBefore( body, docElem.firstChild ); | |
| } | |
| ret = div.offsetWidth; | |
| if( fakeUsed ){ | |
| docElem.removeChild( body ); | |
| } | |
| else { | |
| body.removeChild( div ); | |
| } | |
| // restore the original values | |
| docElem.style.fontSize = originalHTMLFontSize; | |
| if( originalBodyFontSize ) { | |
| body.style.fontSize = originalBodyFontSize; | |
| } | |
| //also update eminpx before returning | |
| ret = eminpx = parseFloat(ret); | |
| return ret; | |
| }, | |
| //enable/disable styles | |
| applyMedia = function( fromResize ){ | |
| var name = "clientWidth", | |
| docElemProp = docElem[ name ], | |
| currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, | |
| styleBlocks = {}, | |
| lastLink = links[ links.length-1 ], | |
| now = (new Date()).getTime(); | |
| //throttle resize calls | |
| if( fromResize && lastCall && now - lastCall < resizeThrottle ){ | |
| w.clearTimeout( resizeDefer ); | |
| resizeDefer = w.setTimeout( applyMedia, resizeThrottle ); | |
| return; | |
| } | |
| else { | |
| lastCall = now; | |
| } | |
| for( var i in mediastyles ){ | |
| if( mediastyles.hasOwnProperty( i ) ){ | |
| var thisstyle = mediastyles[ i ], | |
| min = thisstyle.minw, | |
| max = thisstyle.maxw, | |
| minnull = min === null, | |
| maxnull = max === null, | |
| em = "em"; | |
| if( !!min ){ | |
| min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); | |
| } | |
| if( !!max ){ | |
| max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); | |
| } | |
| // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true | |
| if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ | |
| if( !styleBlocks[ thisstyle.media ] ){ | |
| styleBlocks[ thisstyle.media ] = []; | |
| } | |
| styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); | |
| } | |
| } | |
| } | |
| //remove any existing respond style element(s) | |
| for( var j in appendedEls ){ | |
| if( appendedEls.hasOwnProperty( j ) ){ | |
| if( appendedEls[ j ] && appendedEls[ j ].parentNode === head ){ | |
| head.removeChild( appendedEls[ j ] ); | |
| } | |
| } | |
| } | |
| appendedEls.length = 0; | |
| //inject active styles, grouped by media type | |
| for( var k in styleBlocks ){ | |
| if( styleBlocks.hasOwnProperty( k ) ){ | |
| var ss = doc.createElement( "style" ), | |
| css = styleBlocks[ k ].join( "\n" ); | |
| ss.type = "text/css"; | |
| ss.media = k; | |
| //originally, ss was appended to a documentFragment and sheets were appended in bulk. | |
| //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! | |
| head.insertBefore( ss, lastLink.nextSibling ); | |
| if ( ss.styleSheet ){ | |
| ss.styleSheet.cssText = css; | |
| } | |
| else { | |
| ss.appendChild( doc.createTextNode( css ) ); | |
| } | |
| //push to appendedEls to track for later removal | |
| appendedEls.push( ss ); | |
| } | |
| } | |
| }, | |
| //find media blocks in css text, convert to style blocks | |
| translate = function( styles, href, media ){ | |
| var qs = styles.replace( respond.regex.comments, '' ) | |
| .replace( respond.regex.keyframes, '' ) | |
| .match( respond.regex.media ), | |
| ql = qs && qs.length || 0; | |
| //try to get CSS path | |
| href = href.substring( 0, href.lastIndexOf( "/" ) ); | |
| var repUrls = function( css ){ | |
| return css.replace( respond.regex.urls, "$1" + href + "$2$3" ); | |
| }, | |
| useMedia = !ql && media; | |
| //if path exists, tack on trailing slash | |
| if( href.length ){ href += "/"; } | |
| //if no internal queries exist, but media attr does, use that | |
| //note: this currently lacks support for situations where a media attr is specified on a link AND | |
| //its associated stylesheet has internal CSS media queries. | |
| //In those cases, the media attribute will currently be ignored. | |
| if( useMedia ){ | |
| ql = 1; | |
| } | |
| for( var i = 0; i < ql; i++ ){ | |
| var fullq, thisq, eachq, eql; | |
| //media attr | |
| if( useMedia ){ | |
| fullq = media; | |
| rules.push( repUrls( styles ) ); | |
| } | |
| //parse for styles | |
| else{ | |
| fullq = qs[ i ].match( respond.regex.findStyles ) && RegExp.$1; | |
| rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); | |
| } | |
| eachq = fullq.split( "," ); | |
| eql = eachq.length; | |
| for( var j = 0; j < eql; j++ ){ | |
| thisq = eachq[ j ]; | |
| if( isUnsupportedMediaQuery( thisq ) ) { | |
| continue; | |
| } | |
| mediastyles.push( { | |
| media : thisq.split( "(" )[ 0 ].match( respond.regex.only ) && RegExp.$2 || "all", | |
| rules : rules.length - 1, | |
| hasquery : thisq.indexOf("(") > -1, | |
| minw : thisq.match( respond.regex.minw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), | |
| maxw : thisq.match( respond.regex.maxw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) | |
| } ); | |
| } | |
| } | |
| applyMedia(); | |
| }, | |
| //recurse through request queue, get css text | |
| makeRequests = function(){ | |
| if( requestQueue.length ){ | |
| var thisRequest = requestQueue.shift(); | |
| ajax( thisRequest.href, function( styles ){ | |
| translate( styles, thisRequest.href, thisRequest.media ); | |
| parsedSheets[ thisRequest.href ] = true; | |
| // by wrapping recursive function call in setTimeout | |
| // we prevent "Stack overflow" error in IE7 | |
| w.setTimeout(function(){ makeRequests(); },0); | |
| } ); | |
| } | |
| }, | |
| //loop stylesheets, send text content to translate | |
| ripCSS = function(){ | |
| for( var i = 0; i < links.length; i++ ){ | |
| var sheet = links[ i ], | |
| href = sheet.href, | |
| media = sheet.media, | |
| isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; | |
| //only links plz and prevent re-parsing | |
| if( !!href && isCSS && !parsedSheets[ href ] ){ | |
| // selectivizr exposes css through the rawCssText expando | |
| if (sheet.styleSheet && sheet.styleSheet.rawCssText) { | |
| translate( sheet.styleSheet.rawCssText, href, media ); | |
| parsedSheets[ href ] = true; | |
| } else { | |
| if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) || | |
| href.replace( RegExp.$1, "" ).split( "/" )[0] === w.location.host ){ | |
| // IE7 doesn't handle urls that start with '//' for ajax request | |
| // manually add in the protocol | |
| if ( href.substring(0,2) === "//" ) { href = w.location.protocol + href; } | |
| requestQueue.push( { | |
| href: href, | |
| media: media | |
| } ); | |
| } | |
| } | |
| } | |
| } | |
| makeRequests(); | |
| }; | |
| //translate CSS | |
| ripCSS(); | |
| //expose update for re-running respond later on | |
| respond.update = ripCSS; | |
| //expose getEmValue | |
| respond.getEmValue = getEmValue; | |
| //adjust on resize | |
| function callMedia(){ | |
| applyMedia( true ); | |
| } | |
| if( w.addEventListener ){ | |
| w.addEventListener( "resize", callMedia, false ); | |
| } | |
| else if( w.attachEvent ){ | |
| w.attachEvent( "onresize", callMedia ); | |
| } | |
| })(this); |