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

时间:2021-12-23 08:57:41

引子

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

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

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

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

<!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下显示效果:

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

在Chrome下显示效果:

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

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

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

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

$('#fieldset1').height(200);

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

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

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

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

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

深入 jQuery 源代码

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

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

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

1. 入口函数

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

2. 首先进入 style 函数:

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

3. 进入 set 函数

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

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

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

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

4. 进入 augmentWidthOrHeight 函数:

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

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

5. 最后回到 style 函数:

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

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

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

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

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

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下可以重现问题:

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

简化下代码:

<!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 的源代码:

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

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

说白了,也很简单。通过 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):

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

小结

的的确确,这是 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

点赞

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