Media Queries之Respond.js
一、stackoverflow上面对css3-mediaqueries.js与respond.js的比较
css3-mediaqueries.js
Pros
Supports min, max and min+max mediaqueries
Supports px and em values
Reacts on window resize
Elaborates on-page CSS (style tag) and external stylesheets
Cons
Doesn't support width mediaquery
Doesn't elaborate link[media="screen and ..."] nor @imported stylesheet
respond.js
Pros
Supports min, max and min+max mediaqueries
Supports px and em values
Reacts on window resize
Elaborates external stylesheets only
Cons
Doesn't support width mediaquery
Doesn't elaborate on-page CSS, link [media="screen and ..."] nor @imported stylesheets
It may cause a javascript error when combined with jQuery on load events, to solve it you need to place the script at the end of the page
二、Respond.js源码分析
https://github.com/scottjehl/Respond
A fast & lightweight polyfill for min/max-width CSS3 Media Queries (for IE 6-8, and more)
Respond.js应用的例子,参考http://skinnyties.com
可以在GitHub上可以下载它的未压缩版本respond.src.js(或者将min文件format,不推荐,因为JS压缩后的变量名都是处理过的,不方便阅读),本文使用Fiddler进行本地重定向,分析源码。
1.监听resize事件,以达到改变窗口大小实时响应
function callMedia(){ applyMedia( true ); } if( win.addEventListener ){ // 标准2级DOM事件模型 win.addEventListener( "resize", callMedia, false ); } else if( win.attachEvent ){ // IE事件模型(6、7、8) win.attachEvent( "onresize", callMedia ); }
2.提取CSS文件路径
var doc = win.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" ), requestQueue = [], // 遍历CSS路径 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"; 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 { // 判断条件: // 1.有形如http://这样的href需要判断http://后面的根目录是否等于location.host。 // 2.不以形如http://这样开头的href,同时不包含<base>, // 所以它不支持带有<base>的相对href if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ requestQueue.push( { href: href, media: media } ); } } } } makeRequests(); }
3.发送ajax请求返回CSS内容
// 递归执行获得CSS文本 // 递归配合shift()可以保证最后被遍历的CSS具有最高的优先级 makeRequests = function(){ if( requestQueue.length ){ var thisRequest = requestQueue.shift(); ajax( thisRequest.href, function( styles ){ translate( styles, thisRequest.href, thisRequest.media ); parsedSheets[ thisRequest.href ] = true; // 在递归函数外面包裹一层setTimeout, // 使得函数以异步的方式执行,防止栈溢出 win.setTimeout(function(){ makeRequests(); },0); } ); } } // ajax方法 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 ); }
4.取出CSS中具有形如 @media screen and (max-width: 480px) 的各个块
translate = function( styles, href, media ){ // 匹配各个@media {...} var qs = styles.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ), ql = qs && qs.length || 0; //try to get CSS path href = href.substring( 0, href.lastIndexOf( "/" ) ); var repUrls = function( css ){ return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); }, // useMedia=true表示<link>是否具有media属性并且 // CSS样式中没有@media块 useMedia = !ql && media; //if path exists, tack on trailing slash if( href.length ){ href += "/"; } 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 ) ); } // rules保存了每个@media块内部的样式 else{ fullq = qs[ i ].match( /@media *([^\{]+)\{([\S\s]+?)$/ ) && 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 ]; // 所有media块 mediastyles.push( { media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all", // 对应media块在rules数组中的样式 rules : rules.length - 1, hasquery : thisq.indexOf("(") > -1, minw : thisq.match( /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), maxw : thisq.match( /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) } ); } } applyMedia(); }
5.将匹配的样式加入文档中
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(); // 函数节流,延迟调用 if( fromResize && lastCall && now - lastCall < resizeThrottle ){ win.clearTimeout( resizeDefer ); resizeDefer = win.setTimeout( applyMedia, resizeThrottle ); return; } else { lastCall = now; } // 遍历所有media 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"; // 支持以em为单位的宽度,定义了一个getEmValue方法计算em能换算成多少px 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( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ if( !styleBlocks[ thisstyle.media ] ){ styleBlocks[ thisstyle.media ] = []; } styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); } } } // 删除已存在的respond样式 for( var j in appendedEls ){ if( appendedEls.hasOwnProperty( j ) ){ if( appendedEls[ j ] && appendedEls[ j ].parentNode === head ){ head.removeChild( appendedEls[ j ] ); } } } // 在文档中插入respond样式 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 ){ // IE下 ss.styleSheet.cssText = css; } else { ss.appendChild( doc.createTextNode( css ) ); } //存储在appendedEls中,下次以便跟踪删除 appendedEls.push( ss ); } } }
三、Respond.js带来的跨域请求问题
Respond.js通过ajax请求CSS文件,所以如果CSS文件存放在CDN上面(或者子域中),那么需要引入一个代理页面实现跨域连接。
引入方法:
<!-- Respond.js proxy on external server --> <link href="http://externalcdn.com/respond-proxy.html" id="respond-proxy" rel="respond-proxy" /> <!-- Respond.js redirect location on local server --> <link href="/path/to/respond.proxy.gif" id="respond-redirect" rel="respond-redirect" /> <!-- Respond.js proxy script on local server --> <script src="/path/to/respond.proxy.js"></script>