跳至
/* 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,
p = doc.createelement('p'),
body = doc.body,
originalhtmlfontsize = docelem.style.fontsize,
originalbodyfontsize = body && body.style.fontsize,
fakeused = false;
p.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( p );
if( fakeused ){
docelem.insertbefore( body, docelem.firstchild );
}
ret = p.offsetwidth;
if( fakeused ){
docelem.removechild( body );
}
else {
body.removechild( p );
}
// 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);