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 }; });
做個小廣告:
mass Framework是一個模塊化的jQuery式框架,擁有jQuery 90%的常用方法,在語言處理,類,特效等方面都做了大量增強,是面向大規模開發的框架。現在jQuery也在做瘦身,把許多不常用的方法廢棄掉,這樣一來,大家在DOM處理上的API基本一致。mass Framework預計在年底完成升級,完成自己的MVVM框架與一個支持IE6的bootstrap式UI庫。