我们知道HTML样式定义的三种方式:
<link/>外部引入也就是定义 CSS 中的 <style/>嵌入式样式 style特性地定义
给一个HTML元素设置css属性,如:
var head= document.getElementById("head"); head.style.width = "20px"; head.style.height = "10px"; head.style.display = "block";
这是 DOM2 级样式提供的 API 了,这里总的来说还涉及了3个问题,当然也是 jQuery 内部需要解决与优化的问题。
1.单一的设置太麻烦,而且每次style一次就等于浏览器要绘制一次,当然高级的浏览器是可能会合并 style 的次数的 2.style 接口只能针对行类样式,对于 link 引入的样式,无法获取 3.样式属性名的兼容问题,比如驼峰,保留字 float
任何支持style特性的html元素在js中有一个对象的style属性,其实也是一个实例,但是内部属性命名都是采用驼峰形式的,如background-image要写成backgroundImage,其中一个比较特殊的就是float,保留字,所以就缓存cssFloat,ie:styleFolat,然后对于width,height这些处理都最好有一个量度单位。
合并cssText
var head = document.getElementById("head");
head.style.cssText = "200px;height:200px;";
和innerHTML一样,cssText快捷而且所有浏览器都支持,cssText只需要一次reflow,提高了页面渲染性能,当然如果是在创建的时候,我们还可以利用文档碎片,缺点自然就是样式被整体覆盖了,所以在处理的时候应该要先获取需要保留的样式然后再拼接起来。
样式访问
DOM2规范增加了defaultView接口,提供了getComputedStyle()方法可以返回了类型style属性合集。
getComputedStyle与style的区别
getComputedStyle是只读的,style是可读可写的
css的钩子机制
jquery提供一个api来条用用户自定义的函数,用于扩展,以便获取和设置特定属性值,在attr,prop,val和css的操作中都会引入钩子,钩子都有相似的结构。
var someHook = {
get:function(elem){
return "something";
},
set:function(elem,value){
}
}
在css3属性浏览器兼容的时候,都需要特定的前缀
Webkit内核浏览器:-weblit-border-radius
Firefox内核浏览器:-moz-border-radius
采用css hook可以标准化这些供应商前缀的属性,让css接受一个单一的标准的属性的名称(border-radius或用DOM属性的语法borderRadius)判断的代码省略,给某一元素设置borderRadius为10px.
$("#element").css("borderRadius","10px")
为了做浏览器兼容,我们不得不:
if(webkit){ ........................ }else if(firefox){ ........................... }else if(...)更多
这是一种最没技术含量的写法了,如果我们换成一种 hook 的话:
$.cssHooks.borderRadius = { get: function( elem, computed, extra ) { return $.css( elem, borderRadius ); }, set: function( elem, value) { elem.style[ borderRadius ] = value; } };
$.cssHooks 对象提供了一种方法通过定义函数来获取和设置特定的CSS值的方法,它也可以被用来创建新的 cssHooks 以标准化 CSS3 功能,如框阴影和渐变。
样式操作接口
jquery操作样式的接口jquery.fn.css与jquery.css
css:funtion(name,value){
return value!=nudefined ? jquery.style(elem,name,value) : jquery.css(elem,name);
}
最终方法与设置样式的方法是实例方法,所以我们看传递的参数就知道了,只能是单一的elem,name,value实参。
jquery.style(elem,name,value);
jquery.css(elem,name);
所以如果我们传递是对象或者别的方式就需要一个过滤的环节,css参数的处理也跟以前的attr属性的处理保持一致,无非就是递归参数。
jquery.css()取值
用户传递写css属性名时可以很随意的,backgroundColor,backgound-color,所以框架内部自然要过这个统一的处理。
Query.css()取值
用户传递写 css 属性名是可以很随意的,backgroundColor,backgourd-color,所以框架内部自然要过这个统一的处理。
检测是否是驼峰写法,如果不是就得转化下:
origName = jQuery.camelCase( name );
其次还有一些特殊的属性名的读取问题,比如float就是关键字,就需要转成cssFloat,还有浏览器前缀的命名处理。
name = jQuery.cssProps[origName] || (jQuery.cssProps[origName] = vendorPropName(elem.style, origName));
我们知道元素在 display:none 的情况下是无法获取一些跟布局定位有关的值的,还有不同的元素尺寸width、height、margin获取问题等等,但是这么多情况如何去处理?
jQuery在这里就引入了“css钩子”
hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName];
每一个特定方法都带有各自的处理模式,举个例子如果要获取 ele.css("width")。
那么 cssHooks.width 中就是这样一个结构:
cssHooks = { { get:function(){}, set:function(){} } }
我们看到 cssHooks 几乎都是针对盒子模型的属性处理了,我们考虑下如果用户获取一个元素的 width 会有什么情况?
1. 元素在隐藏 display:none 先获取不到尺寸 2. 元素的宽度,jQuery就有width、innerWidth、outerWidth等各种不同取值方式 3. 元素可以被设置css3的盒子模型
所以针对这一种特定的属性我们需要单独拿一个钩子做处理,那么在取值的时候,我们自然在 cssHooks.width.get 中要判断一下:当元素是否是可见状态,如果不是则要先处理下,在之后判断盒子模型的处理。接下来我们看到如果没有对应的钩子方法,我们模型采用了 curCSS 取值:
if (hooks && "get" in hooks) { val = hooks.get(elem, true, extra); } if (val === undefined) { val = curCSS(elem, name, styles); }
其内部就采用了 defaultView.getComputedStyle(elem, null);最终就是通过 getComputedStyle的getPropertyValue 方法了。
所以总结 jquery.css 的接口其实也很简单。
1.转化样式命名 2.处理特殊的属性比如float 3.分离出一个钩子,用于处理跟尺寸有关的属性 4.其余元素采用getComputedStyle获取对应的值
样式的赋值
样式赋值处理的方式与 css 类似,只是要注意了 style 才具有样式的修改权限,这样的传对象其实都是需要调用多次 style 处理的,当然没有采用 cssText 的方式处理,因为本身以前的属性就会丢失了,值得注意的是,设置样式的时候我们是可以传递一个对象为参数的:
$div.css({ 'padding' : '50', 'margin' : '50' })
可以一次让元素添加多个属性,那么因为我们内部没有采用 cssText 去处理,而是靠的 style 接口,那么意味着就需要针对 jQuery 的参数去修正,换句话说我们就需要通过for in
或者each
去遍历这个参数,分解出每一个属性赋值给 style 接口。针对参数的多形式 jquery 在之前会通过一个 jQuery.access 方法过滤这些参数,原理就是针对字符串、数组、对象的一个遍历而已。
jQuery的处理流程:
1. 分解参数 2. 转换为驼峰式,修正属性名 3. 如果有钩子,则调用钩子的set get 4. 最终实现都是依靠浏览器自己的API的
样式类操作
通过给元素添加删除指定的样式类名用来修改样式的方法有 addClass 与 removeClass,为每个匹配的元素添加指定的样式类名,对元素的样式操作,底层的实现就是修改元素的className值,实现的功能:
增加一条样式规则: .addClass('myClass') 增加多条样式规则: .addClass('myClass yourClass')
传递回调遍历样式规则:
$("ul li:last").addClass(function(index) { return "item-" + index; });
从接口传参去分析这个实现手法,参考右边的代码addClass我把代码简略的分了几个步骤:
- 如果传递的是回调函数,递归调用分解下样式规则,通过正则
/S+/g
空白分组 - 如果元素本身存在 class 样式,先取出来组合成新的规则按照空格分开
- 通过 className 设置新的样式传递一个参数与多个参数的处理无非就是字符串的拼接
这里就不详讲,看看代码就能理解重点说一下传递回调函数的设计,官方给的测试案例:
<p class ='selected highlight'>Goodbye</p> <p class ='selected1 highlight1'>Goodbye</p> <p class ='selected2 highlight2'>Goodbye</p>
增加样式代码:
$(p).addClass(function(index,className){ index className 0 "selected highlight" 1 "selected1 highlight1" 2 "selected2 highlight2" });
遍历出所有的 P 节点,并找其对应的 class,返回给每一个回调函数,看看源码的设计:
//如果传递的是回调函数,递归调用 ⑴ if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { //each addClass回调 jQuery( this ).addClass( value.call( this, j, this.className ) ); }); }
不管是写插件还是其他的,只要是设计操作 DOM 的,在 jQuery 内部就的到 this.each 方法,原因很简单jQuery就是一个数组保存着所有对应的节点的下标。
内部在传递一个编写好的回调函数,传递给 each 方法 ,each 方法理解就是一个循环方法,分解出 jQuery 数组中每一个下标元,然后把每一个元素返回给外部回调。
这里再进一步替换下代码就很明显了:
function( i, obj[ i ] ) { jQuery( this ).addClass( value.call( this, j, this.className ) ) }
这里的 this 是指向的每一个 p 元素节点,因为 callback.call 了一下,把每一个上下文指定到当前的函数了,所以 this 就是对应的 obj[i],最后执行的代码就是:
value.call( this, j, this.className )
value 就是最外层用户定义的回调了,复制代码:
$(p).addClass(function(index,className){ // index className // 0 "selected highlight" // 1 "selected1 highlight1" // 2 "selected2 highlight2" });
这里意外的发现 jQuery Api没给出还包装了一层 jQuery( this ).addClass ,那么意味着 jQuery 还可以接受用户最外层的返回参数,然后再调用 addClass 给当前节点增加新的类名:
jQuery( this ).addClass( value.call( this, j, this.className ) ); p.addClass(function(index,className){ return 'aaaa' });