1 /* Respond.js: min/max-width media query polyfill. (c) Scott Jehl. MIT Lic. j.mp/respondjs */
10 //define update even in native-mq-supporting browsers, to avoid errors
11 respond.update = function(){};
14 var requestQueue = [],
15 xmlHttp = (function() {
16 var xmlhttpmethod = false;
18 xmlhttpmethod = new w.XMLHttpRequest();
21 xmlhttpmethod = new w.ActiveXObject( "Microsoft.XMLHTTP" );
28 //tweaked Ajax functions from Quirksmode
29 ajax = function( url, callback ) {
34 req.open( "GET", url, true );
35 req.onreadystatechange = function () {
36 if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){
39 callback( req.responseText );
41 if ( req.readyState === 4 ){
46 isUnsupportedMediaQuery = function( query ) {
47 return query.replace( respond.regex.minmaxwh, '' ).match( respond.regex.other );
52 respond.queue = requestQueue;
53 respond.unsupportedmq = isUnsupportedMediaQuery;
55 media: /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,
56 keyframes: /@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,
57 comments: /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,
58 urls: /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,
59 findStyles: /@media *([^\{]+)\{([\S\s]+?)$/,
60 only: /(only\s+)?([a-zA-Z]+)\s?/,
61 minw: /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,
62 maxw: /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,
63 minmaxwh: /\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,
67 //expose media query support flag for external use
68 respond.mediaQueriesSupported = w.matchMedia && w.matchMedia( "only all" ) !== null && w.matchMedia( "only all" ).matches;
70 //if media queries are supported, exit here
71 if( respond.mediaQueriesSupported ){
77 docElem = doc.documentElement,
83 head = doc.getElementsByTagName( "head" )[0] || docElem,
84 base = doc.getElementsByTagName( "base" )[0],
85 links = head.getElementsByTagName( "link" ),
90 //cached container for 1em value, populated the first time it's needed
93 // returns the value of 1em in pixels
94 getEmValue = function() {
96 div = doc.createElement('div'),
98 originalHTMLFontSize = docElem.style.fontSize,
99 originalBodyFontSize = body && body.style.fontSize,
102 div.style.cssText = "position:absolute;font-size:1em;width:1em";
105 body = fakeUsed = doc.createElement( "body" );
106 body.style.background = "none";
109 // 1em in a media query is the value of the default font size of the browser
110 // reset docElem and body to ensure the correct value is returned
111 docElem.style.fontSize = "100%";
112 body.style.fontSize = "100%";
114 body.appendChild( div );
117 docElem.insertBefore( body, docElem.firstChild );
120 ret = div.offsetWidth;
123 docElem.removeChild( body );
126 body.removeChild( div );
129 // restore the original values
130 docElem.style.fontSize = originalHTMLFontSize;
131 if( originalBodyFontSize ) {
132 body.style.fontSize = originalBodyFontSize;
136 //also update eminpx before returning
137 ret = eminpx = parseFloat(ret);
142 //enable/disable styles
143 applyMedia = function( fromResize ){
144 var name = "clientWidth",
145 docElemProp = docElem[ name ],
146 currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp,
148 lastLink = links[ links.length-1 ],
149 now = (new Date()).getTime();
151 //throttle resize calls
152 if( fromResize && lastCall && now - lastCall < resizeThrottle ){
153 w.clearTimeout( resizeDefer );
154 resizeDefer = w.setTimeout( applyMedia, resizeThrottle );
161 for( var i in mediastyles ){
162 if( mediastyles.hasOwnProperty( i ) ){
163 var thisstyle = mediastyles[ i ],
164 min = thisstyle.minw,
165 max = thisstyle.maxw,
166 minnull = min === null,
167 maxnull = max === null,
171 min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 );
174 max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 );
177 // 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
178 if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){
179 if( !styleBlocks[ thisstyle.media ] ){
180 styleBlocks[ thisstyle.media ] = [];
182 styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] );
187 //remove any existing respond style element(s)
188 for( var j in appendedEls ){
189 if( appendedEls.hasOwnProperty( j ) ){
190 if( appendedEls[ j ] && appendedEls[ j ].parentNode === head ){
191 head.removeChild( appendedEls[ j ] );
195 appendedEls.length = 0;
197 //inject active styles, grouped by media type
198 for( var k in styleBlocks ){
199 if( styleBlocks.hasOwnProperty( k ) ){
200 var ss = doc.createElement( "style" ),
201 css = styleBlocks[ k ].join( "\n" );
203 ss.type = "text/css";
206 //originally, ss was appended to a documentFragment and sheets were appended in bulk.
207 //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!
208 head.insertBefore( ss, lastLink.nextSibling );
210 if ( ss.styleSheet ){
211 ss.styleSheet.cssText = css;
214 ss.appendChild( doc.createTextNode( css ) );
217 //push to appendedEls to track for later removal
218 appendedEls.push( ss );
222 //find media blocks in css text, convert to style blocks
223 translate = function( styles, href, media ){
224 var qs = styles.replace( respond.regex.comments, '' )
225 .replace( respond.regex.keyframes, '' )
226 .match( respond.regex.media ),
227 ql = qs && qs.length || 0;
229 //try to get CSS path
230 href = href.substring( 0, href.lastIndexOf( "/" ) );
232 var repUrls = function( css ){
233 return css.replace( respond.regex.urls, "$1" + href + "$2$3" );
235 useMedia = !ql && media;
237 //if path exists, tack on trailing slash
238 if( href.length ){ href += "/"; }
240 //if no internal queries exist, but media attr does, use that
241 //note: this currently lacks support for situations where a media attr is specified on a link AND
242 //its associated stylesheet has internal CSS media queries.
243 //In those cases, the media attribute will currently be ignored.
248 for( var i = 0; i < ql; i++ ){
249 var fullq, thisq, eachq, eql;
254 rules.push( repUrls( styles ) );
258 fullq = qs[ i ].match( respond.regex.findStyles ) && RegExp.$1;
259 rules.push( RegExp.$2 && repUrls( RegExp.$2 ) );
262 eachq = fullq.split( "," );
265 for( var j = 0; j < eql; j++ ){
268 if( isUnsupportedMediaQuery( thisq ) ) {
273 media : thisq.split( "(" )[ 0 ].match( respond.regex.only ) && RegExp.$2 || "all",
274 rules : rules.length - 1,
275 hasquery : thisq.indexOf("(") > -1,
276 minw : thisq.match( respond.regex.minw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ),
277 maxw : thisq.match( respond.regex.maxw ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" )
285 //recurse through request queue, get css text
286 makeRequests = function(){
287 if( requestQueue.length ){
288 var thisRequest = requestQueue.shift();
290 ajax( thisRequest.href, function( styles ){
291 translate( styles, thisRequest.href, thisRequest.media );
292 parsedSheets[ thisRequest.href ] = true;
294 // by wrapping recursive function call in setTimeout
295 // we prevent "Stack overflow" error in IE7
296 w.setTimeout(function(){ makeRequests(); },0);
301 //loop stylesheets, send text content to translate
304 for( var i = 0; i < links.length; i++ ){
305 var sheet = links[ i ],
308 isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet";
310 //only links plz and prevent re-parsing
311 if( !!href && isCSS && !parsedSheets[ href ] ){
312 // selectivizr exposes css through the rawCssText expando
313 if (sheet.styleSheet && sheet.styleSheet.rawCssText) {
314 translate( sheet.styleSheet.rawCssText, href, media );
315 parsedSheets[ href ] = true;
317 if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) ||
318 href.replace( RegExp.$1, "" ).split( "/" )[0] === w.location.host ){
319 // IE7 doesn't handle urls that start with '//' for ajax request
320 // manually add in the protocol
321 if ( href.substring(0,2) === "//" ) { href = w.location.protocol + href; }
336 //expose update for re-running respond later on
337 respond.update = ripCSS;
340 respond.getEmValue = getEmValue;
343 function callMedia(){
347 if( w.addEventListener ){
348 w.addEventListener( "resize", callMedia, false );
350 else if( w.attachEvent ){
351 w.attachEvent( "onresize", callMedia );