85

【续】抓个Firefox的小辫子,jQuery表示不背这黑锅,Chrome,Edge,IE8-11继续围观中 -...

 6 years ago
source link: http://www.cnblogs.com/sanshi/p/7932032.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【续】抓个Firefox的小辫子,jQuery表示不背这黑锅,Chrome,Edge,IE8-11继续围观中

昨天我发了一篇文章【抓个Firefox的小辫子,围观群众有:Chrome、Edge、IE8-11】,提到了一个Firefox很多版本都存在的问题,而相同的测试页面在Chrome、Edge、IE8-11下面一切正常。

在评论里面,网友 @Blackheart 的回复引起了我的注意:

48817-20171130213423945-1682453627.png

我就按照网友提供的方法重新测试了一下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<style>
*, :after, :before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
</style>
</head>
<body>
<fieldset id="fieldset1" style="border:solid 1px red;width:500px;position:absolute;top:0;left:0;">
<legend>fieldset</legend>
</fieldset>
<script>
$(function () {
// $('#fieldset1').height(200);
document.getElementById("fieldset1").style.height = "200px";
alert(parseInt($('#fieldset1').height(), 10));
});
</script>
</body>
</html>  

在Firefox下显示效果:

48817-20171130213635914-537369512.png

在Chrome下显示效果:

48817-20171130213755414-1678630441.png

截图中提示 181px,而不是 200px,这是完全正确的:

  • jQuery.height 函数是不包含padding、border和margin的
  • 在box-sizing: border-box;规则下,通过style.height设置的值是包含padding和border的

这两者的差异体现在最终设置的fieldset的高度不同:

$('#fieldset1').height(200);
48817-20171130214608680-630800776.png
document.getElementById("fieldset1").style.height = "200px";
48817-20171130214631836-1468837684.png

  

当然,这两种设置高度的差异不是我们本篇文章讨论的重点。但却是问题的关键所在,下面分析jQuery源代码时会用到这个知识。  

问题好像真的消失了,有那么一刻,我差点就要将Firefox的问题丢给 jQuery 了,但事实果真如此嘛?

深入 jQuery 源代码

俗话说:不入虎穴,焉得虎子,我们就来调试一把 jQuery.height 函数。

虽然用了十来年的jQuery,但真正去调试 jQuery 代码却没有几次,毕竟 jQuery 的代码可谓是千锤百炼,吹毛求疵想找个BUG都难。

这次就没办法了,既然怀疑到这里只好入手了:

1. 入口函数

48817-20171130220831461-1148581894.png

2. 首先进入 style 函数:

48817-20171130221650805-1120196887.png

 

3. 进入 set 函数

48817-20171130220851992-1414993641.png

 

此时如果我们来执行一下 augmentWidthOrHeight:

48817-20171130220938602-1232180376.png

 

得到的是一个负值,通过前面对 jQuery.height 的介绍我们知道,jQuery.height(200)是不包含padding和border的,最终还是要通过 $('#fieldset1')[0].style.height 来设置,所以我们需要先计算 fieldset 上下的padding和border。

4. 进入 augmentWidthOrHeight 函数:

48817-20171130221227852-1542391458.png

这个截图可以印证我们之前的设想,通过 jQuery.css 函数来获取 paddingTop,borderTopWidth,paddingBottom,borderBottomWidth 四个值。

5. 最后回到 style 函数:

48817-20171130221712195-808330075.png

此时 value 值已经是计算后的值了,其中包含用户要设置的 200px,以及上下padding和border 17.6px,总计 217.6px

下面通过 style[ name ] = value,将 217.6px 设置到 fieldset 节点:

48817-20171130221858367-627556909.png

此时,我们来仔细观察下三个变量,说白了,这句话的意思下面的代码是一模一样的:

document.getElementById("fieldset1").style.height = "217.6px";

那就奇怪了,既然 jQuery.height 最终也是调用的 style.height,为啥直接用 style.height 设置就没问题呢?  

最初,我怀疑是 try-catch 搞的鬼,后来发现不是。按照 jQuery.height 的调用流程,我们自己动手来重现一下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<style>
*, :after, :before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
</style>
</head>
<body>
<fieldset id="fieldset1" style="border:solid 1px red;width:500px;position:absolute;top:0;left:0;">
<legend>fieldset</legend>
</fieldset>
<script>
$(function () {
var elem = $('#fieldset1')[0];
var view = elem.ownerDocument.defaultView;
if (!view || !view.opener) {
view = window;
}
var styles = view.getComputedStyle(elem);
var val = 0;
val -= jQuery.css(elem, "paddingTop", true, styles);
val -= jQuery.css(elem, "borderTopWidth", true, styles);
val -= jQuery.css(elem, "paddingBottom", true, styles);
val -= jQuery.css(elem, "borderBottomWidth", true, styles);
elem.style.height = (200 - val) + "px";
alert(parseInt($('#fieldset1').height(), 10));
});
</script>
</body>
</html>  

在Firefox下可以重现问题:

48817-20171130230930133-698573307.png

 

简化下代码:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<style>
*, :after, :before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
</style>
</head>
<body>
<fieldset id="fieldset1" style="border:solid 1px red;width:500px;position:absolute;top:0;left:0;">
<legend>fieldset</legend>
</fieldset>
<script>
$(function () {
var elem = $('#fieldset1')[0];
var styles = window.getComputedStyle(elem);
var val= jQuery.css(elem, "paddingTop", true, styles);
elem.style.height = (200 - val) + "px";
alert(parseInt($('#fieldset1').height(), 10));
});
</script>
</body>
</html>  

Firefox下问题依然存在,很明显是在 jQuery.css 中出现的问题,跟踪到 jQuery 的源代码:

48817-20171130231550914-1507046662.png
48817-20171130233302711-835723946.png

说白了,也很简单。通过 styles.getPropertyValue(name) || styles[name] 来获取某个CSS样式的值。

下面,再次更新示例,去除 jQuery 的相关代码:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<style>
*, :after, :before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
</style>
</head>
<body>
<fieldset id="fieldset1" style="border:solid 1px red;width:500px;position:absolute;top:0;left:0;">
<legend>fieldset</legend>
</fieldset>
<script>
$(function () {
var elem = $('#fieldset1')[0];
var styles = window.getComputedStyle(elem);
var val = parseFloat(styles.getPropertyValue("paddingTop") || styles["paddingTop"]);
elem.style.height = (200 - val) + "px";
alert(parseInt($('#fieldset1').height(), 10));
});
</script>
</body>
</html>    

Firefox问题依然存在,而这里面我们设置 fieldset 的高度根本没用到 jQuery,只是调用了系统的 getCompotedStyle 方法等!!!

中间插播一个广告:

#######################################

#  9 年只做一件事

#  由三生石上亲自打造的 FineUI 控件库,现已支持 ASP.NET Core 2.0,跨平台 Windows、Mac、Linux 都能用!

#  在线示例:http://core.fineui.com/

########################################

广告结束,请继续....

上面还不是最简单,下面我们从页面中完全排除 jQuery ,3 行代码就能重现问题:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style>
*, :after, :before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
</style>
</head>
<body>
<fieldset id="fieldset1" style="border:solid 1px red;width:500px;position:absolute;top:0;left:0;">
<legend>fieldset</legend>
</fieldset>
<script>
window.onload = function() {
var elem = document.getElementById('fieldset1');
window.getComputedStyle(elem)["paddingTop"];
elem.style.height = "200px";
};
</script>
</body>
</html>  

在Chrome和Firefox下的显示效果对比(后面是Chrome,前面是Firefox):

48817-20171130234803570-1606055314.png

的的确确,这是 Firefox Quantum(v57) 以及很多老版本的BUG,和 jQuery 没有关系,jQuery只在做了该做的事情,碰巧遇到这个 Firefox 的BUG而已。

这个BUG在Firefox下重现需要满足如下几个条件:

  1. 设置CSS全局属性:box-sizing: border-box
  2. HTML标签为:fieldset(其他标签没问题,比如div就是正常的)
  3. fieldset绝对定位:position:absolute

在这 3 个条件下,调用JS代码 window.getComputedStyle(elem)["paddingTop"] 之后再设置 fieldset 标签的高度,页面上不会更新!

解决办法请看我的上一篇文章:【原创】抓个Firefox的小辫子,围观群众有:Chrome、Edge、IE8-11

喜欢三石的文章,你就给个推荐呗!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK