Ruby's Louvre

每天学习一点点算法

导航

mass Framework css模块 v4

CSS模块是专门用于读取或设置元素的样式,尺寸,坐标,可选择性,滚动条的模块。

本次升级要点:

  • 把变形部分抽取出来独立成另外的模块。
  • 移除对怪异模式的支持。
  • 重构IE部分的对透明度的读写。
  • 重构IE部分的对选择性(userSelect)的设置。
  • 增加对backgroundPosition的处理。
  • 重构show, hide, toggle方法,全部调用内部的toggelDisplay方法,更方便以后的升级与重构。

经过瘦身后,体积减少二分之一。添加大量有用链接,大家可以通过它们来拓展学习。它们也是本模块或与样式相关的其他模块的重构动力与材料。

css模块的源码:

//=========================================
// 样式操作模块 v4 by 司徒正美
//=========================================
define( "css", !!top.getComputedStyle ? ["$node"] : ["$node","$css_fix"] , function(){
    $.log( "已加载css模块" );
    var adapter = $.cssAdapter || ($.cssAdapter = {})
    var rrelNum = /^([\-+])=([\-+.\de]+)/
    var rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i
 
    adapter["_default:set"] = function( node, name, value){
        node.style[ name ] = value;
    }
    //有关单位转换的 http://heygrady.com/blog/2011/12/21/length-and-angle-unit-conversion-in-javascript/
    if ( window.getComputedStyle ) {
        adapter[ "_default:get" ] = function( node, name ) {
            var ret, width, minWidth, maxWidth, computed = window.getComputedStyle( node, null )
            if (computed ) {
                ret = name == "filter" ? computed.getPropertyValue(name) :computed[name]
                var style = node.style ;
                if ( ret === "" && !$.contains( node.ownerDocument, node ) ) {
                    ret = style[name];//如果还没有加入DOM树,则取内联样式
                }
                //  Dean Edwards大神的hack,用于转换margin的百分比值为更有用的像素值
                // webkit不能转换top, bottom, left, right, margin, text-indent的百分比值
                if (  /^margin/.test( name ) && rnumnonpx.test( ret ) ) {
                    width = style.width;
                    minWidth = style.minWidth;
                    maxWidth = style.maxWidth;

                    style.minWidth = style.maxWidth = style.width = ret;
                    ret = computed.width;
                
                    style.width = width;
                    style.minWidth = minWidth;
                    style.maxWidth = maxWidth;
                }
            };
            return ret === "" ? "auto" : ret;
        }
    }
    var getter = adapter[ "_default:get" ]

    adapter[ "zIndex:get" ] = function( node, name, value, position ) {
        while ( node.nodeType !== 9 ) {
            //即使元素定位了,但如果zindex设置为"aaa"这样的无效值,浏览器都会返回auto;
            //如果没有指定zindex值,IE会返回数字0,其他返回auto
            position = getter(node, "position" );//getter = adapter[ "_default:get" ]
            if ( position === "absolute" || position === "relative" || position === "fixed" ) {
                // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
                value = parseInt( getter(node,"zIndex"), 10 );
                if ( !isNaN( value ) && value !== 0 ) {
                    return value;
                }
            }
            node = node.parentNode;
        }
        return 0;
    }

    //这里的属性不需要自行添加px
    $.cssNumber = $.oneObject("fontSizeAdjust,fontWeight,lineHeight,opacity,orphans,widows,zIndex,zoom,rotate");
    $.css = function( node, name, value){
        if(node.style){//注意string经过call之后,变成String伪对象,不能简单用typeof来检测
            var prop = $.String.camelize(name)
            name = $.cssName( name ) ;
            if( value === void 0){ //获取样式
                return (adapter[ prop+":get" ] || adapter[ "_default:get" ])( node, name );
            }else {//设置样式
                var temp;
                if ( typeof value === "string" && (temp = rrelNum.exec( value )) ) {
                    value =  ( temp[1] + 1) * temp[2]  + parseFloat( $.css( node, name) );
                }
                if ( isFinite( value ) && !$.cssNumber[ prop ] ) {
                    value += "px";
                }
                ;
                (adapter[prop+":set"] || adapter[ "_default:set" ])( node, name, value );
            }
        }
    }

    $.fn.css =  function( name, value , neo){
        return $.access( this, name, value, $.css );
    }

    var cssPair = {
        width:['Left', 'Right'],
        height:['Top', 'Bottom']
    }
    var cssShow = {
        position: "absolute",
        visibility: "hidden",
        display: "block"
    }
    //http://www.cnblogs.com/rubylouvre/archive/2012/10/27/2742529.html
    function showHidden(node, array){
        if( node && node.nodeType == 1 && node.offsetWidth == 0 ){
            if(getter(node, "display") == "none"){
                var obj = {
                    node: node
                }
                for (var name in cssShow ) {
                    obj[ name ] = node.style[ name ];
                    node.style[ name ] = cssShow[ name ];
                }
                array.push( obj );
            }
            showHidden(node.parentNode, array)
        }
    }

    var supportBoxSizing = $.cssName("box-sizing")
    adapter[ "boxSizing:get" ] = function( node, name ) {
        return  supportBoxSizing ? getter(node, name) : document.compatMode == "BackCompat" ?
        "border-box" : "content-box"
    }

    function setWH(node, name, val, extra){
        var which = cssPair[name]
        which.forEach(function(direction){
            if(extra < 1)
                val -= parseFloat(getter(node, 'padding' + direction)) || 0;
            if(extra < 2)
                val -= parseFloat(getter(node, 'border' + direction + 'Width')) || 0;
            if(extra === 3){
                val += parseFloat(getter(node, 'margin' + direction )) || 0;
            } 
            if(extra === "padding-box"){
                val += parseFloat(getter(node, 'padding' + direction)) || 0;
            }
            if(extra === "border-box"){
                val += parseFloat(getter(node, 'padding' + direction)) || 0;
                val += parseFloat(getter(node, 'border' + direction + 'Width')) || 0;
            }
        });
        return val
    }
    function getWH( node, name, extra  ) {//注意 name是首字母大写
        var hidden = [];
        showHidden( node, hidden );
        var  val = setWH(node, name,  node["offset" + name], extra);
        for(var i = 0, obj; obj = hidden[i++];){
            node = obj.node;
            for ( name in obj ) {
                if(typeof obj[ name ] == "string"){
                    node.style[ name ] = obj[ name ];
                }
            }
        }
        return val;
    };
    var rmapper = /(\w+)_(\w+)/g
    //生成width, height, innerWidth, innerHeight, outerWidth, outerHeight这六种原型方法
    "Height,Width".replace( $.rword, function(  name ) {
        var lower = name.toLowerCase(),
        clientProp = "client" + name,
        scrollProp = "scroll" + name,
        offsetProp = "offset" + name;
        $.cssAdapter[ lower+":get" ] = function( node ){
            return getWH( node, name, 0 ) + "px";//添加相应适配器
        }
        $.cssAdapter[ lower+":set" ] = function( node, name, value ){
            var box = $.css(node, "box-sizing");
            node.style[name] = box == "content-box" ? value:
            setWH(node, name, parseFloat(value), box ) + "px";
        }
        "inner_1,b_0,outer_2".replace(rmapper,function(a, b, num){
            var method = b == "b" ? lower : b + name;
            $.fn[ method ] = function( value ) {
                num = b == "outer" && value === true ? 3 : num;
                return $.access( this, num, value, function( node, num, size ) {
                    if ( $.type( node,"Window" ) ) {//取得窗口尺寸,IE9后可以用node.innerWidth /innerHeight代替
                        return node.documentElement[ clientProp ] ;
                    }
                    if ( node.nodeType === 9 ) {//取得页面尺寸
                        var doc = node.documentElement;
                        //FF chrome    html.scrollHeight< body.scrollHeight
                        //IE 标准模式 : html.scrollHeight> body.scrollHeight
                        //IE 怪异模式 : html.scrollHeight 最大等于可视窗口多一点?
                        return Math.max(
                            node.body[ scrollProp ], doc[ scrollProp ],
                            node.body[ offsetProp ], doc[ offsetProp ],
                            doc[ clientProp ]
                            );
                    } else if ( size === void 0 ) {
                        return getWH( node, name, num )
                    } else {
                        return num > 0  ? this : $.css( node, lower, size );
                    }
                }, this)
            }
        })

    });

    var sandbox,sandboxDoc;
    $.callSandbox = function(parent,callback){
        if ( !sandbox ) {
            sandbox = document.createElement( "iframe" );
            sandbox.frameBorder = sandbox.width = sandbox.height = 0;
        }
        parent.appendChild(sandbox);
        if ( !sandboxDoc || !sandbox.createElement ) {
            sandboxDoc = ( sandbox.contentWindow || sandbox.contentDocument ).document;
            sandboxDoc.write( "<!doctype html><html><body>" );
            sandboxDoc.close();
        }
        callback(sandboxDoc);
        parent.removeChild(sandbox);
    }

    var cacheDisplay = $.oneObject("a,abbr,b,span,strong,em,font,i,img,kbd","inline");
    var blocks = $.oneObject("div,h1,h2,h3,h4,h5,h6,section,p","block");
    $.mix(cacheDisplay ,blocks);
    function parseDisplay( nodeName ) {
        nodeName = nodeName.toLowerCase();
        if ( !cacheDisplay[ nodeName ] ) {
            $.callSandbox(document.body, function(doc){
                var  elem = doc.createElement( nodeName );
                doc.body.appendChild( elem );
                cacheDisplay[ nodeName ] = getter( elem, "display" );
            });
        }
        return cacheDisplay[ nodeName ];
    }
    
    function isHidden( elem) {
        return getter( elem, "display" ) === "none" || !$.contains( elem.ownerDocument, elem );
    }

    function toggelDisplay( nodes, show ) {
        var elem,  values = [], status = [], index = 0, length = nodes.length;
        //由于传入的元素们可能存在包含关系,因此分开两个循环来处理,第一个循环用于取得当前值或默认值
        for ( ; index < length; index++ ) {
            elem = nodes[ index ];
            if ( !elem.style ) {
                continue;
            }
            values[ index ] = $._data( elem, "olddisplay" );
            status[ index ] = isHidden(elem) 
            if( !values[ index ] ){
                values[ index ] =  status[index] ? defaultDisplay(elem.nodeName): 
                getter(elem, "display");
                $._data( elem, "olddisplay", values[ index ])
            }
        }
        //第二个循环用于设置样式,-1为toggle, 1为show, 0为hide
        for ( index = 0; index < length; index++ ) {
            elem = nodes[ index ];
            if ( !elem.style ) {
                continue;
            }
            show = show === -1 ? !status[index] : show
            elem.style.display = show ?  values[ index ] : "none";
        }
        return nodes;
    }
    $.fn.show =  function() {
        return toggelDisplay( this, 1 );
    }
    $.fn.hide = function() {
        return toggelDisplay( this, 0 );
    }
    //state为true时,强制全部显示,为false,强制全部隐藏
    $.fn.toggle = function( state ) {
        return toggelDisplay( this, typeof state == "boolean" ? state : -1 );
    }

    function setOffset(node, options){
        if(node && node.nodeType == 1 ){
            var position = $.css( node, "position" );
            //强逼定位
            if ( position === "static" ) {
                node.style.position = "relative";
            }
            var curElem = $( node ),
            curOffset = curElem.offset(),
            curCSSTop = $.css( node, "top" ),
            curCSSLeft = $.css( node, "left" ),
            calculatePosition = ( position === "absolute" || position === "fixed" ) &&  [curCSSTop, curCSSLeft].indexOf("auto") > -1,
            props = {}, curPosition = {}, curTop, curLeft;
            if ( calculatePosition ) {
                curPosition = curElem.position();
                curTop = curPosition.top;
                curLeft = curPosition.left;
            } else {
                //如果是相对定位只要用当前top,left做基数
                curTop = parseFloat( curCSSTop ) || 0;
                curLeft = parseFloat( curCSSLeft ) || 0;
            }

            if ( options.top != null ) {
                props.top = ( options.top - curOffset.top ) + curTop;
            }
            if ( options.left != null ) {
                props.left = ( options.left - curOffset.left ) + curLeft;
            }
            curElem.css( props );
        }
    }

    $.fn.offset = function(options){//取得第一个元素位于页面的坐标
        if ( arguments.length ) {
            return (!options || ( !isFinite(options.top) && !isFinite(options.left) ) ) ?  this :
            this.each(function() {
                setOffset( this, options );
            });
        }

        var node = this[0], doc = node && node.ownerDocument, pos = {
            left:0,
            top:0
        };
        if ( !doc ) {
            return pos;
        }
        //http://hkom.blog1.fc2.com/?mode=m&no=750 body的偏移量是不包含margin的
        //我们可以通过getBoundingClientRect来获得元素相对于client的rect.
        //http://msdn.microsoft.com/en-us/library/ms536433.aspx
        var box = node.getBoundingClientRect(),win = getWindow(doc),
        root = doc.documentElemen,
        clientTop  = root.clientTop  || 0,
        clientLeft = root.clientLeft || 0,
        scrollTop  = win.pageYOffset ||  root.scrollTop  ,
        scrollLeft = win.pageXOffset ||  root.scrollLeft ;
        // 把滚动距离加到left,top中去。
        // IE一些版本中会自动为HTML元素加上2px的border,我们需要去掉它
        // http://msdn.microsoft.com/en-us/library/ms533564(VS.85).aspx
        pos.top  = box.top  + scrollTop  - clientTop,
        pos.left = box.left + scrollLeft - clientLeft;

        return pos;
    }

    $.fn.position = function() {//取得元素相对于其offsetParent的坐标
        var offset, offsetParent , node = this[0],
        parentOffset = {//默认的offsetParent相对于视窗的距离
            top: 0,
            left: 0
        }
        if ( !node ||  node.nodeType !== 1 ) {
            return
        }
        //fixed 元素是相对于window
        if(getter( node, "position" ) === "fixed" ){
            offset  = node.getBoundingClientRect();
        } else {
            offset = this.offset();//得到元素相对于视窗的距离(我们只有它的top与left)
            offsetParent = this.offsetParent();
            if ( offsetParent[ 0 ].tagName !== "HTML"  ) {
                parentOffset = offsetParent.offset();//得到它的offsetParent相对于视窗的距离
            }
            parentOffset.top  += parseFloat( getter( offsetParent[ 0 ], "borderTopWidth" ) ) || 0;
            parentOffset.left += parseFloat( getter( offsetParent[ 0 ], "borderLeftWidth" ) ) || 0;
        }
        return {
            top:  offset.top  - parentOffset.top - ( parseFloat( getter( node, "marginTop" ) ) || 0 ),
            left: offset.left - parentOffset.left - ( parseFloat( getter( node, "marginLeft" ) ) || 0 )
        };
    }
    //https://github.com/beviz/jquery-caret-position-getter/blob/master/jquery.caretposition.js
    //https://developer.mozilla.org/en-US/docs/DOM/element.offsetParent
    //如果元素被移出DOM树,或display为none,或作为HTML或BODY元素,或其position的精确值为fixed时,返回null
    $.fn.offsetParent = function() {
        return this.map(function() {
            var el = this.offsetParent;
            while ( el && (el.parentNode.nodeType !== 9 ) && getter(el, "position") === "static" ) {
                el = el.offsetParent;
            }
            return el || document.documentElement;
        });
    }
    $.fn.scrollParent = function() {
        var scrollParent;
        if ((window.VBArray && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
            scrollParent = this.parents().filter(function() {
                return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
            }).eq(0);
        } else {
            scrollParent = this.parents().filter(function() {
                return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
            }).eq(0);
        }
        return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
    }

    "scrollLeft_pageXOffset,scrollTop_pageYOffset".replace( rmapper, function(_, method, prop ) {
        $.fn[ method ] = function( val ) {
            var node, win, top = method == "scrollTop";
            if ( val === void 0 ) {
                node = this[ 0 ];
                if ( !node ) {
                    return null;
                }
                win = getWindow( node );//获取第一个元素的scrollTop/scrollLeft
                return win ? (prop in win) ? win[ prop ] :
                win.document.documentElement[ method ]  : node[ method ];
            }
            return this.each(function() {//设置匹配元素的scrollTop/scrollLeft
                win = getWindow( this );
                if ( win ) {
                    win.scrollTo(
                        !top ? val : $( win ).scrollLeft(),
                        top ? val : $( win ).scrollTop()
                        );
                } else {
                    this[ method ] = val;
                }
            });
        };
    });
    var pseudoAdapter = window.VBArray && $.query && $.query.pseudoAdapter
    if(pseudoAdapter){
        pseudoAdapter.hidden = function( el ) {
            return el.type === "hidden" || $.css( el, "display") === "none" ;
        }
    }

    function getWindow( node ) {
        return $.type(node,"Window") ?   node : node.nodeType === 9 ? node.defaultView || node.parentWindow : false;
    } ;
});

css模块依赖于node模块的cssName与cssMap,它们是框架支持CSS3新样式的关键。

css_fix模块源码(它是用于对旧式IE的支持——IE6-8)

//=========================================
//  样式补丁模块
//==========================================
define("css_fix", !!top.getComputedStyle, function(){
    $.log("已加载css_fix模块");
    var adapter = $.cssAdapter = {},
    ie8 = !!top.XDomainRequest,
    rfilters = /[\w\:\.]+\([^)]+\)/g,
    salpha = "DXImageTransform.Microsoft.Alpha",
    rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
    rposition = /^(top|right|bottom|left)$/,
    border = {
        thin:   ie8 ? '1px' : '2px',
        medium: ie8 ? '3px' : '4px',
        thick:  ie8 ? '5px' : '6px'
    };
    adapter[ "_default:get" ] = function(node, name){
        //取得精确值,不过它有可能是带em,pc,mm,pt,%等单位
        var ret = node.currentStyle[name];
        if (( rnumnonpx.test(ret) && !rposition.test(ret))) {
            //①,保存原有的style.left, runtimeStyle.left,
            var style = node.style, left = style.left,
            rsLeft =  node.runtimeStyle.left ;
            //②由于③处的style.left = xxx会影响到currentStyle.left,
            //因此把它currentStyle.left放到runtimeStyle.left,
            //runtimeStyle.left拥有最高优先级,不会style.left影响
            node.runtimeStyle.left = node.currentStyle.left;
            //③将精确值赋给到style.left,然后通过IE的另一个私有属性 style.pixelLeft
            //得到单位为px的结果;fontSize的分支见http://bugs.jquery.com/ticket/760
            style.left = name === 'fontSize' ? '1em' : (ret || 0);
            ret = style.pixelLeft + "px";
            //④还原 style.left,runtimeStyle.left
            style.left = left;
            node.runtimeStyle.left = rsLeft;
        }
        if( ret == "medium" ){
            name = name.replace("Width","Style");
            //border width 默认值为medium,即使其为0"
            if(arguments.callee(node,name) == "none"){
                ret = "0px";
            }
        }
        //处理auto值
        if(rposition.test(name) && ret === "auto"){
            ret = "0px";
        }
        return ret === "" ? "auto" : border[ret] ||  ret;
    }
    //========================= 处理 opacity =========================
    adapter[ "opacity:get" ] = function( node ){
        //这是最快的获取IE透明值的方式,不需要动用正则了!
        var alpha = node.filters.alpha || node.filters[salpha],
        op = alpha ? alpha.opacity: 100;
        return ( op /100 )+"";//确保返回的是字符串
    }
    //http://www.freemathhelp.com/matrix-multiplication.html
    adapter[ "opacity:set" ] = function( node, name, value ){
        var currentStyle = node.currentStyle, style = node.style;
        if(!isFinite(value)){//"xxx" * 100 = NaN
            return
        }
        value = (value > 0.999) ? 100: (value < 0.001) ? 0 : value * 100;
        if(!currentStyle.hasLayout)
            style.zoom = 1;//让元素获得hasLayout
        var filter = currentStyle.filter || style.filter || "";
        //http://snook.ca/archives/html_and_css/ie-position-fixed-opacity-filter
        //IE78的透明滤镜当其值为100时会让文本模糊不清
        if(value == 100  ){  //IE78的透明滤镜当其值为100时会让文本模糊不清
            // var str =  "filter: progid:DXImageTransform.Microsoft.Alpha(opacity=100) Chroma(Color='#FFFFFF')"+
            //   "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',"+
            //   "M11=1.5320888862379554, M12=-1.2855752193730787,  M21=1.2855752193730796, M22=1.5320888862379558)";
            value = style.filter = filter.replace(rfilters, function(a){
                return /alpha/i.test(a) ? "" : a;//可能存在多个滤镜,只清掉透明部分
            });
            //如果只有一个透明滤镜 就直接去掉
            if(value.trim() == "" && style.removeAttribute){
                style.removeAttribute( "filter" );
            }
            return;
        }
        //如果已经设置过透明滤镜可以使用以下便捷方式
        var alpha = node.filters.alpha || node.filters[salpha];

        if( alpha ){
            alpha.opacity = value ;
        }else{
            style.filter  += (filter ? "," : "")+ "alpha(opacity="+ value +")";
        }
    }
    //========================= 处理 user-select =========================
    //auto——默认值,用户可以选中元素中的内容
    //none——用户不能选择元素中的任何内容
    //text——用户可以选择元素中的文本
    //element——文本可选,但仅限元素的边界内(只有IE和FF支持)
    //all——在编辑器内,如果双击/上下文点击发生在子元素上,改值的最高级祖先元素将被选中。
    //-moz-none——firefox私有,元素和子元素的文本将不可选,但是,子元素可以通过text重设回可选。
    adapter[ "userSelect:set" ] = function( node, name, value ) {
        var allow = /none/.test(value) ? "on" : "",
        e, i = 0, els = node.getElementsByTagName('*');
        node.setAttribute('unselectable', allow);
        while (( e = els[ i++ ] )) {
            switch (e.tagName.toLowerCase()) {
                case 'iframe' :
                case 'textarea' :
                case 'input' :
                case 'select' :
                    break;
                default :
                    e.setAttribute('unselectable', allow);
            }
        }
    };
    //========================= 处理 background-position =========================
    adapter[ "backgroundPosition:get" ] = function( node, name, value ) {
        var style = node.currentStyle;
        return style.backgroundPositionX +" "+style.backgroundPositionX
    };

});

github地址


做个小广告:

mass Framework是一个模块化的jQuery式框架,拥有jQuery 90%的常用方法,在语言处理,类,特效等方面都做了大量增强,是面向大规模开发的框架。现在jQuery也在做瘦身,把许多不常用的方法废弃掉,这样一来,大家在DOM处理上的API基本一致。mass Framework预计在年底完成升级,完成自己的MVVM框架与一个支持IE6的bootstrap式UI库。

posted on 2012-11-02 09:01  司徒正美  阅读(2504)  评论(4编辑  收藏  举报