第九章:样式模块

| 阅读数:--次| 作者:js,javascript
摘要:样式模块分为两大块,精确获取样式值与设置样式,精确是用于修饰符获取的。由于样式分布为外部样式,内部样式与行内样式,再加个impotant对选择器的权重的干扰,我们实际很难看到元素是应用了那块的样式。因此,样式模块,80%的比重在于获取这一块,像offset,滚动条也纳入这一块。大体上,我们在标准浏览...

样式模块分为两大块,精确获取样式值与设置样式,精确是用于修饰符获取的。由于样式分布为外部样式,内部样式与行内样式,再加个impotant对选择器的权重的干扰,我们实际很难看到元素是应用了那块的样式。因此,样式模块,80%的比重在于获取这一块,像offset,滚动条也纳入这一块。

大体上,我们在标准浏览器是使用getComputedStyle,ie6-8使用currentStyle来获取元素的精确样式。不过,getComputedStyle并不挂在元素上,而是window的一个API,它返回一个对象,可以选择使用getPropertyValue方法传入连字符风格的样式名取得其值 ,或者属性法+驼峰风格的样式名去取值。但考虑到currentStyle也是使用属性法+驼峰风格,我们就统一使用后者。

var getStyle = function (el, name) {
        if (el.style) {
            name = name.replace(/\-(\w)/g,function(all,letter) {
                return letter.toUpperCase();
            });
            if (window.getComputedStyle) {
                //getComputedStyle的第二个伪类是用于对付伪类的,如滚动条,placeholder,
                //但ie9不支持,因为我们只管元素节点,上面的el.style过滤掉了
                return el.ownerDocument.getComputedStyle(el, null)[name]
            } else {
                return el.currentStyle[ name ]
            }
        }
    }

设置样式则更是没有难度,直接el.style[name]=value搞定

但框架要考虑的东西更多,如兼容性,易用性,扩展性

1.样式名要同时支持连字符风格(css的标准风格),与驼峰风格(DOM的标准风格)

2.样式名要进行必要的处理,如float样式与css3带私有前缀的样式

3.如果框架是仿jQuery风格,要考虑set all get frist

4.设置样式时,对长度宽度可以考虑直接处理数值,比如由框架智能补上px单位。

5.设置样式时,对于长宽度可以考虑传入相对值,如"-=20"

6.对于个别的样式特殊处理,如IE下的z-index,opacity,user-select,background-position,top,left

7.基于setStyle,getStyle的扩展,height,width,offset等方法。

在学习过程中,对css模块与css_fix模块进行展开。涵盖的内容相当于jQuery的css,offset,demensions模块,也相当于EXT4的Element.style Element.scroll, Element.position模块

https://github.com/jiayi2/mass-Framework/blob/master/css.js
https://github.com/jiayi2/mass-Framework/blob/master/css_fix.js

其中,css_fix用于兼容旧版的IE,涉及的API有css,cssName,cssNumber,cssHooks,height,width,innerHeight,innerWidth,outerWidth,offset,position,scrollTop,scrollLeft,show,hide,toggle,offsetParent,scrollParent。

放在$上为静态方法,放在$.fn上的为原型方法。独立于这两者的为私有方法或对象

一.样式名的修正

不是所有的样式名都是用正则简单的处理一下就行,这里存在三个陷阱,float对于的javascript属性存在兼容性问题css3带来的私有前缀ie的私有前缀不和流等问题

私有前缀是css3的实现和标准滞后所带来的问题,其实私有前缀-ms-在ie8时代就存在了,-khtml-就更早了。现在私有前缀如下:

浏览器 ie firefox chrome safari opera Konqueror
前缀 -ms- -moz- -webkit- -webkit- -o- -khtml-

2013年,google嫌webkit内核态臃肿,决定自己单干,取名blink,并在chrome28起使用此内核,为了减轻用户负担,还是使用webkit做前缀,目前,2013年,google嫌webkit内核态臃肿,决定自己单干,取名blink,并在chrome28起使用此内核,为了减轻用户负担,还是使用webkit做前缀,目前,使用-webkit-前缀的有Opera,Safari,Chrome三家。

上述的这些前缀加上样式名再驼峰化就是真正可用的样式名,比如-ms-transform -> MsTransform, -webkit-transform ->WebKitTransform, -o-transform -> Otransform 。
这些实验性的样式会迟早退出历史舞台,它们会卸载掉前缀从新亮相,比如ff17下就可以直接使用transfrom。但光是这样不够,还有第三个问题IE下的-ms-trabsform的javascript属性名msTransform,因此,搞定这个正则就特别复杂,我们要动用一个函数通过侦测手段获取它。在mass,它叫cssName,在jQuery,它叫vendorPropName。由于特征侦测是DOM操作,消耗很大,因此获取后就应该缓存起来,避免重复检测,这个对象在mass称为cssMap.

var prefixes = ['', '-webkit-', '-moz-', '-ms-', '-o-'];
    var cssMap = {
        "float": $.support.cssFloat ? 'cssFloat' : 'styleFloat', background : "backgroundColor"
    };

    function cssName(name, host, camelCase) {
        if (cssMap[name]) {
            return cssMap[name];
        }
        host = host || document.documentElement
        for (var i = 0, n = prefixes.length; i < n; i++) {
            camelCase = $.String.camelCase(prefixes[i] + name);
            if (camelCase in host) {
                return (cssMap[name] = camelCase)
            }
        }
        return null;
    }

prefixes的顺序设置得相当有技巧,“”表示没有私有前缀, 此样式已经标准化,所有在最前。-webkit- , -o-依次排开。

通过上面的函数,我们只需传入一个参数,就可以得到真正可用的样式名了。

一:个别样式的特殊处理

让我们来展示cssHooks的价值所在,它是专门用于对付那些有兼容性的问题,不按常规出牌的奇葩样式。cssHooks为一个普通的对象。每个属性名都以xxx+":set"或xxx+":get"命名,值为处理函数。

1。opacity。

.opacity{opacity:.5}
opacity会让背景与内容变得透明想内容不透明就要用rgba与hsla。不过它们是一种值的格式,并不是样式。

ie依赖滤镜DXImageTransform.Microsoft.Alpha,不过IE提供了一个简短的

.opacity{
fliter:alpha(opacity=40)

}

在ie6 7需要注意,为了使得透明设置生效,元素必须是“有布局”。一个元素可以通过一些css属性来使其被布局,有如width和position.关于微软专有的hasLayout属性详情,以及如何触发它,可以看下面的链接。

http://www.blueidea.com/tech/site/2006/3698.asp

2.background-position

旧版ie中,ie只支持backgrounPositionX与backgroundPositionY,不支持backgroundPosition。实现很简单,分别提取backgrounPositionX与backgroundPositionY,然后拼合他们。

adapter["backgroundPosition:get"] = function(node, name, value) {
        var style = node.currentStyle;
        return style.backgroundPositionX + " " + style.backgroundPositionY
    };

3.z-index

z-index是一个并不难理解的属性,但它因为错误的假设使一些开发人员陷入混乱。混乱发生的原因是因为z-index只能工作在被明确定义了absolute,fixed,relative这三个定位属性元素中,它会让元素沿着z轴进行排序(z轴的起点为父节点所在的层,终点为屏幕)。

z-index在下拉菜单,tooltip,灯箱效果中,相册与拖动中经常被使用为了让目标控件排在最前,我们需要得知他们的z-index,然后有目的的改z-index,然后有目的的改z-index或重排元素(将目标元素移除dom树,再插入父元素最后的一个元素之后)

想获取z-index,这里应对一个特殊情况目标元素没有被定位,需要往上回溯到其祖先定位元素。如果找到,就返回祖先的z-index.如果最后也没找到,就返回0.

adapter["zIndex:get"] = function (node) {
        while (node.nodeType !== 9) {
            //即使元素定为了,但如果z-index的值设置为 "aaa"这样的无效值。浏览器都会返回auto
            //如果没有指定z-index值,ie会返回0。其它返回auto
        var position = getter(node, "position") || "static";
        if (position !== "static") {
            // <div style="z-index:-10;" ><div style="z-index:0;"></div></div>
            var value = parseInt(getter(node, "zIndex"),10);
            if (!isNaN(value) && value !== 0) {
                return value;
            }
        }
        node = node.parentNode;
       }
       return 0
    }

4.元素的隐显

元素的隐藏与显示在页面上实现由很多办法,这里只说明下display。display为none时,它不再占有物理空间,附近的元素就顺势挪过去,比如手风琴效果,下拉效果都依赖于此

$.fn.show = function() {
        return this.each(function() {
            this.style.display = "";
        })
    }

    $.fn.hide = function() {
        return this.each(function() {
            this.style.display = "none";
        })
    }

    $.fn.toggle = function() {
        return this.each(function() {
            this.style.display = isHidden(this) ? "" : "none";
        })
    }

    $.fn.isHidden = function(node) {
        return node.sourceIndex === 0 ||
        getter(node, "display") === "none" ||
        !$.contains(node.ownerDocument, node);
    }

然后我们创建一个方法,把$.fn.show, $.fn.hide, $.fn.toggle功能全部交给它做。

function toggleDisplay(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] ? $.parseDispaly(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] : "none";
    }
    return nodes
    }

最后,我们只要稍微在外面一层就能实现与jQuery功能一样的show,hide,toggle,这样做还有一个好处,就是接口与实现分离实现隐藏于内部保持既有功能不受太多制约安心优化与升级

$.fn.show = function() {
        return toggleDisplay (this, 1);
    };

    $.fn.hide = function() {
        return toggleDisplay (this, 0);
    }
    //state为true时,强制全部显示,为false时,强制全部隐藏
    $.fn.toggle = function() {
        return toggleDisplay (this, typeof state === "boolean" ? state : -1)
    }

此文未完结,请关注我,即将更新:

5.元素的坐标(基于getBoundingClientRect方法)

6.元素的滚动条坐标

 

上一章:第八章:节点模块 下一章:第十章:属性模块

返回顶部
学到老代码浏览 关闭浏览