文章出處

引子

昨天我發了一篇文章【抓個Firefox的小辮子,圍觀群眾有:Chrome、Edge、IE8-11】,提到了一個Firefox很多版本都存在的問題,而相同的測試頁面在Chrome、Edge、IE8-11下面一切正常。

在評論里面,網友 @Blackheart 的回復引起了我的注意:

 

我就按照網友提供的方法重新測試了一下:

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

在Chrome下顯示效果:

截圖中提示 181px,而不是 200px,這是完全正確的:

  • jQuery.height 函數是不包含padding、border和margin的
  • 在box-sizing: border-box;規則下,通過style.height設置的值是包含padding和border的

這兩者的差異體現在最終設置的fieldset的高度不同:

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

 

 

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

  

 

當然,這兩種設置高度的差異不是我們本篇文章討論的重點。但卻是問題的關鍵所在,下面分析jQuery源代碼時會用到這個知識。  

 

問題好像真的消失了,有那么一刻,我差點就要將Firefox的問題丟給 jQuery 了,但事實果真如此嘛?

 

深入 jQuery 源代碼

俗話說:不入虎穴,焉得虎子,我們就來調試一把 jQuery.height 函數。

雖然用了十來年的jQuery,但真正去調試 jQuery 代碼卻沒有幾次,畢竟 jQuery 的代碼可謂是千錘百煉,吹毛求疵想找個BUG都難。

這次就沒辦法了,既然懷疑到這里只好入手了:

1. 入口函數

 

2. 首先進入 style 函數:

 

 

3. 進入 set 函數

 

 

此時如果我們來執行一下 augmentWidthOrHeight:

 

得到的是一個負值,通過前面對 jQuery.height 的介紹我們知道,jQuery.height(200)是不包含padding和border的,最終還是要通過 $('#fieldset1')[0].style.height 來設置,所以我們需要先計算 fieldset 上下的padding和border。

 

4. 進入 augmentWidthOrHeight 函數:

 

這個截圖可以印證我們之前的設想,通過 jQuery.css 函數來獲取 paddingTop,borderTopWidth,paddingBottom,borderBottomWidth 四個值。

 

5. 最后回到 style 函數:

 

此時 value 值已經是計算后的值了,其中包含用戶要設置的 200px,以及上下padding和border 17.6px,總計 217.6px

下面通過 style[ name ] = value,將 217.6px 設置到 fieldset 節點:

此時,我們來仔細觀察下三個變量,說白了,這句話的意思下面的代碼是一模一樣的:

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下可以重現問題:

 

簡化下代碼:

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

 

 

說白了,也很簡單。通過 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 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

 

點贊

喜歡三石的文章,你就給個推薦唄!

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()