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