帖子折叠后无法展开:浮点数比较惨案

@Ta 2019-04-30发布,2019-04-30修改 5451点击

我在jhin主题经常遇到帖子内容自动折叠后无法展开的问题,在手机UC上遇到过,在电脑火狐上也遇到过。不过大多数时候只要刷新一下就好了,我也没太在意,直到我遇到了这个贴:

https://hu60.cn/q.php/bbs.topic.89312.html

嗯,就是这个贴,在我修复之前,用电脑版火狐以140%比例放大,无论怎么点击“展开隐藏内容”,内容都无法展开。

用于折叠的代码是这样的:

$(document).ready(function(){
// 自动折叠过长内容
var maxHeight = 768;
$(".topic-content,.floor-content").each(function(){
    var that =$(this);
    var id=this.getAttribute("data-floorID");
    if(that.height() >  maxHeight){
        that.height(maxHeight);
        $('#floor_fold_bar_'+id).html("<button data-floorID='"+id+"'>展开隐藏内容</button>");
        $('#floor_fold_bar_'+id+">button").on('click',function(){
            var id=this.getAttribute("data-floorID");
            var that=$("#floor_content_"+id)
            if(that.height()>maxHeight){
                that.height(maxHeight);
                this.innerHTML='展开超出内容';
            }else{
                that.height(that[0].scrollHeight);
                this.innerHTML='折叠超出内容';
            }
        });
    }
});

我盯着它大眼瞪小眼的看了很久,怎么看都觉得没问题啊。但是同样的帖子,在经典主题里面就没问题,展开折叠无比顺滑(虽然没有动画效果)。经典主题的代码是这样的:

function foldFold(floor) {
    var content = document.getElementById('floor_content_' + floor);
    var foldBar = document.getElementById('floor_fold_bar_' + floor);
    
    content.style.maxHeight = '768px';
    foldBar.innerHTML = '<a id="floor_expand_' + floor +
            '" href="#" onclick="foldExpand(' + floor + ');return false">查看全部</a>';
}
function foldExpand(floor) {
    var content = document.getElementById('floor_content_' + floor);
    var foldBar = document.getElementById('floor_fold_bar_' + floor);
    
    content.style.maxHeight = '';
    foldBar.innerHTML = '<a id="floor_fold_' + floor +
            '" href="#" onclick="foldFold(' + floor + ');return false">折叠内容</a>';
}
function foldFloorInit(floor) {
    var content = document.getElementById('floor_content_' + floor);
    var height = content.offsetHeight;
    
    if (height > 768) {
        var foldBar = document.getElementById('floor_fold_bar_' + floor);
        
        foldBar.style.borderTop = '1px solid #BED8EA';
        foldBar.style.borderBottom = '1px solid #BED8EA';
        foldBar.style.height = '24px';
        foldBar.style.textAlign = 'center';
        
        foldFold(floor);
    }
}
function foldFloorOnload(floorSize) {
    var i;
    
    for (i=0; i<floorSize; i++) {
        foldFloorInit(i);
    }
}

我想了很多种可能,比如JQuery未正常加载、that[0].scrollHeight与火狐兼容性不好等等,但随后都被一一否决了。最核心的问题是:只要我稍微改变一下网页的放大倍数,问题就不会出现。这个问题看起来只在140%放大比例下出现,所以JQuery加载和兼容性都不太可能。

然后,我尝试把that.height(maxHeight);改成that.css('max-height', maxHeight+'px');,但还是只在140%放大比例下出问题。万般无奈之下,我只好打开脚本调试器。然后我就看到了惊人的运行轨迹:

that.height()>maxHeight???

明明that.height(maxHeight);已经被反复执行了好几次,但接下来的每一次that.height()>maxHeight判断都为真???!!!

于是我把console.log(that.height(), maxHeight, that.height()>maxHeight);添加到了代码中,结果如下:

答案显而易见了,因为that.height()是浮点数,即使将其设置为768,结果也可能不是768,而是比768大或者小那么一点点。然后比较的时候就……

而这个问题之所以只在特定的放大比例下出现,是因为其他比例下浏览器的内部浮点实现恰好可以精确存储768px,或者存储后的结果比768小(767.9999999...),而140%放大比例下对768px(实际为768x1.4=1075.2px)的存储结果恰好比768大。。。。。。

于是果断改成了

if(this.innerHTML == '折叠超出内容'){
    that.height(maxHeight);
    this.innerHTML='展开超出内容';
}else{
    that.height(that[0].scrollHeight);
    this.innerHTML='折叠超出内容';
}

问题解决,展开功能恢复。

这件事情再次提醒了我,浮点数不能精确比较大小,不仅是不能精确比较相等,也不能精确比较大于或者小于。在任一操作数为浮点型或者可能为浮点型的情况下,做差取绝对值是唯一安全的办法。

// 不会出问题的比较方法
// 相当于 if (that.height() == maxHeight)
if (Math.abs(that.height() - maxHeight) < 0.1)
回复列表(8|隐藏机器人聊天)
添加新回复
回复需要登录