hjg
2024-07-09 30304784e82d4bba24121328da8eb8490aec4f4f
提交 | 用户 | 时间
58d006 1 /* Respond.js: min/max-width media query polyfill. (c) Scott Jehl. MIT Lic. j.mp/respondjs  */
A 2 (function( w ){
3
4     "use strict";
5
6     //exposed namespace
7     var respond = {};
8     w.respond = respond;
9
10     //define update even in native-mq-supporting browsers, to avoid errors
11     respond.update = function(){};
12
13     //define ajax obj
14     var requestQueue = [],
15         xmlHttp = (function() {
16             var xmlhttpmethod = false;
17             try {
18                 xmlhttpmethod = new w.XMLHttpRequest();
19             }
20             catch( e ){
21                 xmlhttpmethod = new w.ActiveXObject( "Microsoft.XMLHTTP" );
22             }
23             return function(){
24                 return xmlhttpmethod;
25             };
26         })(),
27
28         //tweaked Ajax functions from Quirksmode
29         ajax = function( url, callback ) {
30             var req = xmlHttp();
31             if (!req){
32                 return;
33             }
34             req.open( "GET", url, true );
35             req.onreadystatechange = function () {
36                 if ( req.readyState !== 4 || req.status !== 200 && req.status !== 304 ){
37                     return;
38                 }
39                 callback( req.responseText );
40             };
41             if ( req.readyState === 4 ){
42                 return;
43             }
44             req.send( null );
45         },
46         isUnsupportedMediaQuery = function( query ) {
47             return query.replace( respond.regex.minmaxwh, '' ).match( respond.regex.other );
48         };
49
50     //expose for testing
51     respond.ajax = ajax;
52     respond.queue = requestQueue;
53     respond.unsupportedmq = isUnsupportedMediaQuery;
54     respond.regex = {
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,
64         other: /\([^\)]*\)/g
65     };
66
67     //expose media query support flag for external use
68     respond.mediaQueriesSupported = w.matchMedia && w.matchMedia( "only all" ) !== null && w.matchMedia( "only all" ).matches;
69
70     //if media queries are supported, exit here
71     if( respond.mediaQueriesSupported ){
72         return;
73     }
74
75     //define vars
76     var doc = w.document,
77         docElem = doc.documentElement,
78         mediastyles = [],
79         rules = [],
80         appendedEls = [],
81         parsedSheets = {},
82         resizeThrottle = 30,
83         head = doc.getElementsByTagName( "head" )[0] || docElem,
84         base = doc.getElementsByTagName( "base" )[0],
85         links = head.getElementsByTagName( "link" ),
86
87         lastCall,
88         resizeDefer,
89
90         //cached container for 1em value, populated the first time it's needed
91         eminpx,
92
93         // returns the value of 1em in pixels
94         getEmValue = function() {
95             var ret,
96                 div = doc.createElement('div'),
97                 body = doc.body,
98                 originalHTMLFontSize = docElem.style.fontSize,
99                 originalBodyFontSize = body && body.style.fontSize,
100                 fakeUsed = false;
101
102             div.style.cssText = "position:absolute;font-size:1em;width:1em";
103
104             if( !body ){
105                 body = fakeUsed = doc.createElement( "body" );
106                 body.style.background = "none";
107             }
108
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%";
113
114             body.appendChild( div );
115
116             if( fakeUsed ){
117                 docElem.insertBefore( body, docElem.firstChild );
118             }
119
120             ret = div.offsetWidth;
121
122             if( fakeUsed ){
123                 docElem.removeChild( body );
124             }
125             else {
126                 body.removeChild( div );
127             }
128
129             // restore the original values
130             docElem.style.fontSize = originalHTMLFontSize;
131             if( originalBodyFontSize ) {
132                 body.style.fontSize = originalBodyFontSize;
133             }
134
135
136             //also update eminpx before returning
137             ret = eminpx = parseFloat(ret);
138
139             return ret;
140         },
141
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,
147                 styleBlocks    = {},
148                 lastLink = links[ links.length-1 ],
149                 now = (new Date()).getTime();
150
151             //throttle resize calls
152             if( fromResize && lastCall && now - lastCall < resizeThrottle ){
153                 w.clearTimeout( resizeDefer );
154                 resizeDefer = w.setTimeout( applyMedia, resizeThrottle );
155                 return;
156             }
157             else {
158                 lastCall = now;
159             }
160
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,
168                         em = "em";
169
170                     if( !!min ){
171                         min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 );
172                     }
173                     if( !!max ){
174                         max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 );
175                     }
176
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 ] = [];
181                         }
182                         styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] );
183                     }
184                 }
185             }
186
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 ] );
192                     }
193                 }
194             }
195             appendedEls.length = 0;
196
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" );
202
203                     ss.type = "text/css";
204                     ss.media = k;
205
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 );
209
210                     if ( ss.styleSheet ){
211                         ss.styleSheet.cssText = css;
212                     }
213                     else {
214                         ss.appendChild( doc.createTextNode( css ) );
215                     }
216
217                     //push to appendedEls to track for later removal
218                     appendedEls.push( ss );
219                 }
220             }
221         },
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;
228
229             //try to get CSS path
230             href = href.substring( 0, href.lastIndexOf( "/" ) );
231
232             var repUrls = function( css ){
233                     return css.replace( respond.regex.urls, "$1" + href + "$2$3" );
234                 },
235                 useMedia = !ql && media;
236
237             //if path exists, tack on trailing slash
238             if( href.length ){ href += "/"; }
239
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.
244             if( useMedia ){
245                 ql = 1;
246             }
247
248             for( var i = 0; i < ql; i++ ){
249                 var fullq, thisq, eachq, eql;
250
251                 //media attr
252                 if( useMedia ){
253                     fullq = media;
254                     rules.push( repUrls( styles ) );
255                 }
256                 //parse for styles
257                 else{
258                     fullq = qs[ i ].match( respond.regex.findStyles ) && RegExp.$1;
259                     rules.push( RegExp.$2 && repUrls( RegExp.$2 ) );
260                 }
261
262                 eachq = fullq.split( "," );
263                 eql = eachq.length;
264
265                 for( var j = 0; j < eql; j++ ){
266                     thisq = eachq[ j ];
267
268                     if( isUnsupportedMediaQuery( thisq ) ) {
269                         continue;
270                     }
271
272                     mediastyles.push( {
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 || "" )
278                     } );
279                 }
280             }
281
282             applyMedia();
283         },
284
285         //recurse through request queue, get css text
286         makeRequests = function(){
287             if( requestQueue.length ){
288                 var thisRequest = requestQueue.shift();
289
290                 ajax( thisRequest.href, function( styles ){
291                     translate( styles, thisRequest.href, thisRequest.media );
292                     parsedSheets[ thisRequest.href ] = true;
293
294                     // by wrapping recursive function call in setTimeout
295                     // we prevent "Stack overflow" error in IE7
296                     w.setTimeout(function(){ makeRequests(); },0);
297                 } );
298             }
299         },
300
301         //loop stylesheets, send text content to translate
302         ripCSS = function(){
303
304             for( var i = 0; i < links.length; i++ ){
305                 var sheet = links[ i ],
306                 href = sheet.href,
307                 media = sheet.media,
308                 isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet";
309
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;
316                     } else {
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; }
322                             requestQueue.push( {
323                                 href: href,
324                                 media: media
325                             } );
326                         }
327                     }
328                 }
329             }
330             makeRequests();
331         };
332
333     //translate CSS
334     ripCSS();
335
336     //expose update for re-running respond later on
337     respond.update = ripCSS;
338
339     //expose getEmValue
340     respond.getEmValue = getEmValue;
341
342     //adjust on resize
343     function callMedia(){
344         applyMedia( true );
345     }
346
347     if( w.addEventListener ){
348         w.addEventListener( "resize", callMedia, false );
349     }
350     else if( w.attachEvent ){
351         w.attachEvent( "onresize", callMedia );
352     }
353 })(this);