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

查看skinnyties.com源代码,可以看到
4-1

可以在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上面(或者子域中),那么需要引入一个代理页面实现跨域连接。

GitHub地址

引入方法:

<!-- 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>
posted @ 2013-08-12 19:06  赵东禹  阅读(1963)  评论(0编辑  收藏  举报