/ JavaScript

jQuery的.css函数的一个BUG

其实关于jQuery的css函数的BUG在很久以前的一篇博文中就已经有所提到,不过那个已经在1.4.3版本中被修复了,只可惜又引入了一个新的BUG,并且直到jQuery1.4.4都没有修复,昨天看了玉伯的从 isPlainObject 到“完美”代码的实现一文,仔细从各方面考虑了一下,感觉这确实是一个BUG,因此在本篇再提及一下。

这个BUG的解释相当简单,就是一句话:

在IE中,当一个元素已经拥有filter样式时,使用$('#bug').css('opacity', value)会使原有的filter样式失效

问题的重现

重现这个问题,需要以下的HTML片断:

<div class="filter-img"></div>
<div id="bug" class="filter-img">

配上以下的CSS:

div.filter-img {
    width: 200px;
    height: 258px;
    background: #f00;
    filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=img.png");
}

最后使用下面的Javascript:

$('#bug').css('opacity', 0.6);

期望的内容是2张图片,第二张有半透明的效果,然而在IE中展现出下图所示的效果:

BUG效果图

可以看到因为使用了jQuery的css设置opacity,导致原先通过AlphaImageLoader滤镜加载的图片消失了。

问题的成因

查看jQuery 1.4.4的源码,在无压缩版本的5468行可以看到以下内容:

// 此处的ralpha正则为/alpha\([^)]*\)/i
style.filter = ralpha.test(filter) ?
    filter.replace(ralpha, opacity) :
    style.filter + ' ' + opacity;

代码片断比较容易理解,当原filter已经存在Alpha滤镜时,直接修改其中的Opacity值,不存在时,通过一个空格分隔,添加上一个Alpha滤镜。

但是很遗憾,filter变量是通过style.filter获取的,而经测试证明,在IE6-8中获得的都是空字符串,因此Alpha滤镜会覆盖原有的所有滤镜效果。不明白jQuery团队为何在此不使用currentStyle以获取真正的filter值……

从场景出发

玉伯在他的博文中说,要从场景出发,去判断是不是BUG,深以为然。

那么是否存在这样一种场景,需要在原本已经有filter的情况下添加上Alpha滤镜呢?笔者认为是存在的。

从最简单的角度考虑,可以简单地认为部分滤镜有着其相对应的CSS样式的实现,以下是一个简单的表格:

滤镜 CSS样式
Alpha opacity
AlphaImageLoader background-image
Matrix transform
DropShadow text-shadow
Shadow box-shadow

因此,对于场景问题的回答,也可以简化成“以上的CSS样式有没有可能同时使用”,答案显然是肯定的。一个半透明的图片、一段半透明的带阴影的文字、一个半透明的带阴影效果并有一定角度的旋转的元素,都是很正常的设计。所以将这个问题视为jQuery的一个BUG,笔者认为一点也不为过。

修复

感谢jQuery 1.4.4的CSS Hook功能,不需要直接修改jQuery的源码就能修复这个问题,将下面的代码保存为单独的js文件并在jQuery之后引入即可:

(function($) {
    if ($.support.opacity) {
        return;
    }

    var ralpha = /alpha\([^)]*\)/i;

    $.cssHooks.opacity['set'] = function(elem, value) {
        var style = elem.style,
            // 因为只有IE6-8才不支持opacity,因此currentStyle保证可用
            currentStyle = elem.currentStyle;

        style.zoom = 1;

        var opacity = jQuery.isNaN(value) ?
            "" :
            "alpha(opacity=" + value * 100 + ")",
            filter = currentStyle.filter || "";

        style.filter = ralpha.test(filter) ?
            filter.replace(ralpha, opacity) :
            currentStyle.filter + ' ' + opacity;
    };
}(jQuery));