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
};
});
做个小广告:
mass Framework是一个模块化的jQuery式框架,拥有jQuery 90%的常用方法,在语言处理,类,特效等方面都做了大量增强,是面向大规模开发的框架。现在jQuery也在做瘦身,把许多不常用的方法废弃掉,这样一来,大家在DOM处理上的API基本一致。mass Framework预计在年底完成升级,完成自己的MVVM框架与一个支持IE6的bootstrap式UI库。
机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
浙公网安备 33010602011771号