zoukankan      html  css  js  c++  java
  • [原创] jQuery源码分析13 CSS操作CSS样式表jQuery.fn.css()

    作者:nuysoft/高云 QQ:47214707 Email:nuysoft@gmail.com
    声明:本文为原创文章,如需转载,请注明来源并保留原文链接。

    jQuery源码分析系列(持续更新)

    样式表

    概述

    如何使用

    使用详解

        特性对应的实现原理

    源码分析

        .css( name, value )

        jQuery.style( elem, name, value, extra )

        jQuery.css( elem, name, extra )

        curCSS( elem, name )

    概述

    CSS操作部分的源码分析基于版本1.7.1,以后的jQuery源码分析系列将采用最新的版本。

    jQuery.fn.css()主要解决了三个问题:

    浏览器兼容:IE、W3C

    兼容HTML样式属性和DOM样式属性:连词符、驼峰

    使设置元素的样式属性变得快速简单:动态参数检测、设置和读取用统一的接口

    如何使用

    jQuery.fn.css()有4种用法,第一种是读取样式属性值,其余三种是设置样式属性值

    .css( propertyName ) 获取第一个元素的样式属性值,propertyName是CSS属性名

    .css( propertyName, value ) 在匹配的元素集上设置一个CSS属性,value是要设置的属性值

    .css( propertyName, function(index, value) ) 将函数返回值做为属性值设置

        function(index, value) 返回要设置的属性值,函数的上下文this指向当前元素,接收两个参数:index是当前元素在集合中的下标位置;value是旧值,即当前值(意味着设置之前要先取出当前值)

    .css( map ) 设置多个样式

        map 含有键值对的map,键是属性名,值是字面直接量或函数

    使用详解

    读取和设置样式表遇到的难题是浏览器兼容性。例如访问样式属性时的方式就不同

    // 在基于标准的浏览器中
    var defaultView = elem && elem.ownerDocument.defaultView;
    var computedStyle = defaultView && defaultView.getComputedStyle( elem, null );
    var ret = computedStyle && computedStyle.getPropertyValue( name );
    return ret;
    // 相对的在IE中
    var ret = elem.currentStyle && elem.currentStyle[ name ]
    return ret;

    另一个常见的兼容问题是,某些属性在不同的浏览器中使用不同的属性名,例如float,在IE的DOM实现中用styleFloat,而在遵守W3C标准的浏览器中是 cssFloat。

    jQuery的.css()方法封装了这些差异,无论使用什么属性名都返回相同的结果。例如,一个向左浮动的元素,下边的三行代码每行都会返回字符串left:

    $('div.left').css('float');
    $('div.left').css('cssFloat');
    $('div.left').css('styleFloat');

    如果遇到由多个单词组成的属性,这些属性在CSS和DOM有着不一样的格式,jQuery也能等价的解释,例如:

    .css( { 'background-color': '#ffe', 'border-left': '5px solid #ccc' } )
    .css( { backgroundColor: '#ffe', borderLeft: '5px solid #ccc' } )

    jQuery都能识别并返回正确的值,注意在DOM属性的引号是可选的,而CSS属性必须有引号,因为在属性名中有连字符-

    当使用.css()设置样式时,jQuery改变元素的样式属性style property,例如下面两行代码是等价的:

    $('#mydiv').css('color', 'green')
    document.getElementById('mydiv').style.color = 'green'

    为样式属性设置一个空字符串,例如$('#mydiv').css('color', ''),如果这个属性是行内样式(HTML style attribute),这个属性会被从元素的style中移除,无论是通过.css()方法操作,还是直接操作DOM样式属性style(DOM style property);但是如果是定义在外部样式表stylesheet或内部样式表<style>元素中则不会移除(jQuery的实现并不会修改外部样式表和内部样式表,这一点并不像有些书上写的,尽管浏览器提供了原生API支持)。

    如果遇到CSS color,不同的浏览器可能返回逻辑上相等但是字面上不同的颜色值,总共有四种格式:#FFF、#ffffff、rgb(255,255,255)、blue。

    但是.css()不支持CSS属性缩写,例如margin background border。例如,如果想要获取外边距,需要使用$(elem).css('marginTop') 和 $(elem).css('marginRight'),其他以此类推。

    从jQuery1.6开始,.css()可以支持相对值,就像.animate()。相对值是以+=或-=开头的字符串,表示对当前值增加或减少。例如:一个元素的padding-left是10px,.css("padding-left", "+=15")使padding-left变为25px。

    从jQuery1.4开始,.css()允许传入一个函数作为属性值,例如在下面的这个例子中,将匹配元素的宽度设置为不断增大的值(递增):

    $('elem.example').css('width', function(index) {
      return index * 50;
    });

    注意:如果函数没有返回任何值(例如function( index, style ){}),或返回undefined,当前值不会改变。这一点很有用,如果需要只要当满足一定条件时,选择性的设置属性值时(函数不返回值或返回undefined,与返回空字符串,有着截然不同的处理逻辑和结果)。

    最后补充一点CSS的基础知识,参考http://wenku.baidu.com/view/d9a18f7e27284b73f2425089.html:

    1. CSS样式表有三种写法:行内样式、文档内部样式、文档外部样式

    2. 样式优先级:行内样式> 内部样式 > 外部样式,ID选择器 > class选择器

    将以上特性对应的实现原理简单阐述下(后边的源码分析会详细的解释):

    设置和读取用都通过.css()

    通过调用多功能工具函数jQuery.access(详见03 构造jQuery对象-工具函数

    访问样式属性时的方式不同

    jQuery加载执行时检测浏览器特性,将getComputedStyle或currentStyle统一为jQuery内部方法curCSS()

    某些属性在不同的浏览器中使用不同的属性名

    jQuery.cssProps中定义了属性名之间的映射关系

    多个单词组成的样式属性在CSS和DOM有着不一样的格式

    通过方法jQuery.camelCase()将连词符格式转为驼峰格式

    相对值

    通过jQuery内部正则rrelNum = /^([\-+])=([\-+.\de]+)/检测并提取运算符和相对值,然后计算

    函数的返回值作为属性值

    通过调用多功能工具函数jQuery.access执行函数,并将返回值传给jQuery.style

    源码分析

    .css( name, value )

    jQuery.fn.css = function( name, value ) {
    	// Setting 'undefined' is a no-op
    	// 两个参数,value为undefined,则不做任何操作,返回this
    	// 即如果将一个样式属性设为undefined,不做任何操作
    	if ( arguments.length === 2 && value === undefined ) {
    		return this;
    	}
    	// access: function( elems, key, value, exec, fn, pass ) {
    	// 调用多功能工具函数jQuery.access,对this进行遍历,并执行参数中的函数
    	return jQuery.access( this, name, value, true, function( elem, name, value ) {
    		return value !== undefined ?
    			jQuery.style( elem, name, value ) : // 设值,包括value是空字符串
    			jQuery.css( elem, name ); // 取值
    
    	});
    };

    .css()依赖于三个方法:

    jQuery.access() 这个全局方法支持.css()、.attr()、.prop(),分析详见03 构造jQuery对象-工具函数

    jQuery.style() 在DOM节点上读取或设置样式属性(style property)

    jQuery.css() 在DOM元素上读取DOM样式值

    马上开始剖析jQuery.style()和jQuery.css()。

    jQuery.style( elem, name, value, extra )

    jQuery.style()负责在DOM节点上读取或设置样式属性style property(事实上在CSS模块中只用了jQuery.style()的设置功能,读取功能和jQuery.css()有什么区别么?有待继续研究!)。

    这个方法大致做了如下事

    1. 过滤Text和Comment,过滤无style的元素,返回undefined

    2. 转换为驼峰式,修正属性名

    3. 如果是设置:

        如果是number,过滤NaN;过滤null;如果是相对值字符串,计算

        添加后缀

        如果存在钩子,则调用钩子的set;如果没有钩子,则设置style[ name ] = value;

    4. 如果是读取:

        如果存在钩子,则调用钩子的get;如果没有钩子,则返回style[ name ]

    看看源码注释

    // Get and set the style property on a DOM Node
    // 在DOM节点上读取或设置样式属性style property
    style: function( elem, name, value, extra ) {
    	// Don't set styles on text and comment nodes
    	// 过滤Text和Comment,如果没有style属性也返回
    	if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
    		return; // undefined
    	}
    
    	// Make sure that we're working with the right name
    	// 确保使用了正确的名字
    	var ret, type, origName = jQuery.camelCase( name ),
    		style = elem.style, hooks = jQuery.cssHooks[ origName ]; // 转换为驼峰格式
    	// 修正属性名,是否在不同的浏览器中使用不同的属性名
    	name = jQuery.cssProps[ origName ] || origName; // CSS钩子
    
    	// Check if we're setting a value
    	// 设置
    	if ( value !== undefined ) {
    		type = typeof value;
    
    		// convert relative number strings (+= or -=) to relative numbers. #7345
    		// 计算相对值 rrelNum = /^([\-+])=([\-+.\de]+)/, //
    		if ( type === "string" && (ret = rrelNum.exec( value )) ) {
    			/*
    		 	* ret[1] 正负;ret[2] 相对值
    		 	* +( ret[1] + 1) ret[1]是字符串,加上1变成'+1'或'-1',最前边的加号将字符串转换为数字1或-1
    			 * +ret[2] 同样的加号将ret[2]转换为数字
    		 	* 正负1 乘以 相对值 再加上 当前值,得出要设置的值
    			 */
    			value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
    			// Fixes bug #9237
    			// #9237:.css()在带有连字符的属性上不能工作,在1.6.2中修正
    			type = "number";
    		}
    
    		// Make sure that NaN and null values aren't set. See: #7116
    		// 过滤NaN null,不做任何处理,如果想从内联样式中删除某个属性,请传入空字符串
    		if ( value == null || type === "number" && isNaN( value ) ) {
    			return;
    		}
    
    		// If a number was passed in, add 'px' to the (except for certain CSS properties)
    		// 如果传入一个数字,追加单位px(jQuery.cssNumber中定义的属性除外,见jQuery.cssNumber的定义)
    		if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
    			value += "px";
    		}
    
    		// 前边的都是前戏:过滤非法参数、计算相对值、追加单位后缀
    
    		// If a hook was provided, use that value, otherwise just set the specified value
    		/*
    		 * 如果有钩子hooks,且hooks中存在set函数,则调用hooks.set,将返回值赋给value
    		 * 如果hooks.set的返回值为undefined,则不执行任何操作;返回值不为undefined,则用新value设置样式值
    		 * 简单点说,有hooks.set则调用,用返回值替换value,最后设置style.name;否则直接设置style.name
    		 * 可见钩子的作用是修正属性值,并不直接对值进行设置
    		 * 等价的逻辑:
    		 * <pre>
    		 * if ( hooks && "set" in hooks ) {
    		 *   value = hooks.set( elem, value );
    		 *   if( value != undefined ) style[ name ] = value;
    		 * } else {  
    		 *   style[ name ] = value;
    		 * }
    		 * </pre>
    		 */
    		if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
    			// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
    			// Fixes bug #5509
    			// 用try-catch块,预防在IE中,当用不合法的值设置样式值时,抛出异常
    			try {
    				style[ name ] = value;
    			} catch(e) {}
    		}
    	// 读取
    	} else {
    		// If a hook was provided get the non-computed value from there
    		// 如果有钩子hooks,则调用hooks.get,返回值赋给ret
    		if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
    			return ret;
    		}
    
    		// Otherwise just get the value from the style object
    		// 否则从style对象中读取属性值
    		return style[ name ];
    	}
    }

    jQuery.css( elem, name, extra )

    jQuery.css() 负责读取样式值。

    这个方法大致做了如下事

    1. 转换为驼峰式,修正属性名

    2. 如果有钩子,则调用钩子的get

    3. 否则调用curCSS,不同的浏览器调用不同的方法:

        IE:getComputedStyle,elem.ownerDocument.defaultView.getComputedStyle( elem, null ).getPropertyValue( name )

        W3C:currentStyle,elem.currentStyle[ name ]

    看看源码注释

    // 读取样式值
    css: function( elem, name, extra ) {
    	var ret, hooks;
    
    	// Make sure that we're working with the right name
    	name = jQuery.camelCase( name ); // 转换为驼峰式
    	hooks = jQuery.cssHooks[ name ]; // 是否有钩子
    	name = jQuery.cssProps[ name ] || name; // 修正属性名
    
    	// cssFloat needs a special treatment
    	// cssFloat需要特殊处理,(styleFloat不需要吗?)
    	if ( name === "cssFloat" ) {
    		name = "float"; // 又把它转换回去了!
    	}
    
    	// If a hook was provided get the computed value from there
    	// 如果钩子hooks存在,则调用hooks.get计算样式值,并返回
    	if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
    		return ret;
    
    	// Otherwise, if a way to get the computed value exists, use that
    	// 否则,如果curCSS存在,则调用curCSS获取计算后的样式值,并返回
    	} else if ( curCSS ) {
    		return curCSS( elem, name );
    	}
    }

    curCSS( elem, name )

    /**
     * 标准
     */
    if ( document.defaultView && document.defaultView.getComputedStyle ) {
    	getComputedStyle = function( elem, name ) {
    		var ret, defaultView, computedStyle; // 预定义变量
    		// 将驼峰式转换为连字符,例如marginTop > margin-top
    		// rupper = /([A-Z]|^ms)/g,
    		name = name.replace( rupper, "-$1" ).toLowerCase();
    
    		/*
    		 * 分解:
    		 * var defaultView = elem && elem.ownerDocument.defaultView;
    		 * var computedStyle = defaultView && defaultView.getComputedStyle( elem, null );
    		 * var ret = computedStyle && computedStyle.getPropertyValue( name );
    		 * return ret;
    		 */
    		if ( (defaultView = elem.ownerDocument.defaultView) &&
    				(computedStyle = defaultView.getComputedStyle( elem, null )) ) {
    			ret = computedStyle.getPropertyValue( name );
    			// 看不懂这行在干什么?
    			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
    				ret = jQuery.style( elem, name );
    			}
    		}
    
    		return ret;
    	};
    }
    /**
     * IE
     */
    if ( document.documentElement.currentStyle ) {
    	currentStyle = function( elem, name ) {
    		var left, rsLeft, uncomputed,
    			ret = elem.currentStyle && elem.currentStyle[ name ], // 直接就取值
    			style = elem.style;
    
    		// Avoid setting ret to empty string here
    		// so we don't default to auto
    		/*
    		 * 避免返回空字符串,看不懂?
    		 * 如果elem.currentStyle[ name ]返回null,用style[name]试试
    		 */
    		if ( ret === null && style && (uncomputed = style[ name ]) ) {
    			ret = uncomputed;
    		}
    
    		// From the awesome hack by Dean Edwards
    		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
    
    		// If we're not dealing with a regular pixel number
    		// but a number that has a weird ending, we need to convert it to pixels
    		/*
    		 * 不处理一般的像素值,但是如果单位很奇怪就需要修正为像素px
    		 * rnumpx = /^-?\d+(?:px)?$/i, // 可选的负号 加 数字 加 可选的px,对数值进行检查
    		 * rnum = /^-?\d/, // 整数,不支持+1这样的写法(应该支持)
    		 * 
    		 * 数字后跟了非像素单位
    		 * 
    		 * 后边的看不懂啊,应该是修正单位、auto、fontSize
    		 */
    		if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
    
    			// Remember the original values
    			// 记录原始值
    			left = style.left;
    			rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; // 
    
    			// Put in the new values to get a computed value out
    			if ( rsLeft ) { 
    				elem.runtimeStyle.left = elem.currentStyle.left;
    			}
    			style.left = name === "fontSize" ? "1em" : ( ret || 0 );
    			ret = style.pixelLeft + "px";
    
    			// Revert the changed values
    			style.left = left;
    			if ( rsLeft ) {
    				elem.runtimeStyle.left = rsLeft;
    			}
    		}
    
    		return ret === "" ? "auto" : ret;
    	};
    }
    
    curCSS = getComputedStyle || currentStyle;

    后记:本人对CSS能熟练使用但不精通,看源码的过程遇到很多疑问,不懂的地方文中有标记,各位同学如果能解疑或有好的参考资料多多拍砖。本人、本文、本系列均不是权威,请持怀疑态度。

  • 相关阅读:
    [webpack]解决报错 CleanWebpackPlugin is not a constructor
    awk匹配案例
    ftp的主动模式和被动模式区别:
    为正在运行的容器添加端口映射
    第三章 pod:运行于kubernetes中的容器
    kubernetes介绍
    k8s基础整理-标签/注解/命名空间
    MySQL5.7修改用户密码
    基于kubernetes v1.17部署dashboard:v2.0-beta8
    使用OpenSSL生成自签名SSL证书
  • 原文地址:https://www.cnblogs.com/aaa6818162/p/2302992.html
Copyright © 2011-2022 走看看