吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6450|回复: 34
收起左侧

[其他原创] [原创]论坛公告栏的调试

[复制链接]
Ganlv 发表于 2018-2-9 21:36
本帖最后由 wushaominkk 于 2018-4-3 11:02 编辑

水帖,水帖,水帖。多喝水,更健康。

问题

01.jpg

这个公告栏在我的电脑上会错位,在手机上就不会

分析

根据经验,这个东西有两种可能,一是小数的舍入出现问题,而是高分辨率屏幕的问题。

调试

F12 打开开发者工具。定位到这个元素,看看 CSS 出现了什么问题。

02.jpg

比较奇怪的是,我把这个元素周围都找遍了,就是没有找到 CSS 的改变。

于是,我只能从代码开始分析了。

这个部分会在鼠标放在上面的时候停止滚动,所以我找一下 mouseoverEventListener

03.jpg

然后用左下角的 {} 按钮格式化代码。

Chrome 的开发者工具很人性化嘛。这个开发者工具可是相当牛逼了,真要是有教程的话,这都能写一本书。

04.jpg

格式化之后,就会定位到 EventListener 的位置。

05.jpg

我大致看了一下,这个代码使用的是 overflow: hiddenscrollTop 结合做出的滚动效果。并没有用到 CSS,并不是用 margin-topposition: absolutetop 或者 transform: translate(0,y) 的方法做的。

我们把代码复制出来,看看出了什么问题。

function announcement() {
    var ann = new Object();
    ann.anndelay = 3000;
    ann.annst = 0;
    ann.annstop = 0;
    ann.annrowcount = 0;
    ann.anncount = 0;
    ann.annlis = $('anc').getElementsByTagName("li");
    ann.annrows = new Array();
    ann.announcementScroll = function() {
        if (this.annstop) {
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
            return;
        }
        if (!this.annst) {
            // 以下部分为初始化代码
            var lasttop = -1;
            for (i = 0; i < this.annlis.length; i++) {
                if (lasttop != this.annlis[i].offsetTop) {
                    if (lasttop == -1)
                        lasttop = 0;
                    // 计算两个元素的 offsetTop 的差值
                    this.annrows[this.annrowcount] = this.annlis[i].offsetTop - lasttop;
                    this.annrowcount++;
                }
                lasttop = this.annlis[i].offsetTop;
                // 上面这一部分就是计算两个元素的 offsetTop 的差值
            }
            if (this.annrows.length == 1) {
                $('an').onmouseover = $('an').onmouseout = null;
            } else {
                this.annrows[this.annrowcount] = this.annrows[1];
                $('ancl').innerHTML += $('ancl').innerHTML;
                this.annst = setTimeout(function() {
                    ann.announcementScroll();
                }, this.anndelay);
                $('an').onmouseover = function() {
                    // 鼠标悬浮时暂停
                    ann.annstop = 1;
                }
                ;
                $('an').onmouseout = function() {
                    ann.annstop = 0;
                }
                ;
            }
            this.annrowcount = 1;
            return;
        }
        if (this.annrowcount >= this.annrows.length) {
            $('anc').scrollTop = 0;
            this.annrowcount = 1;
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
        } else {
            this.anncount = 0;
            // 滚动 this.annrows[this.annrowcount] 像素
            this.announcementScrollnext(this.annrows[this.annrowcount]);
        }
    }
    ;
    ann.announcementScrollnext = function(time) {
        // 向下滚动 1 像素
        $('anc').scrollTop++;
        // 计数器 +1
        this.anncount++;
        if (this.anncount != time) {
            this.annst = setTimeout(function() {
                // 继续滚动
                ann.announcementScrollnext(time);
            }, 10);
        } else {
            // 滚动条目 +1
            this.annrowcount++; 
            this.annst = setTimeout(function() {
                // 准备下一次滚动
                ann.announcementScroll();
            }, this.anndelay);
        }
    }
    ;
    ann.announcementScroll();
}

分析了一下代码,并不存在什么问题。

作为一个程序员,看到这句代码是非常反感的。

$('anc').scrollTop++;
this.anncount++;
if (this.anncount != time)

这种动画过程的东西,最好不要用“累加小量 + 次数判断”的方法,因为如果是浮点数,这里累加就容易出现偏差。

最好采用“系统时间均分总时间”的方法,这样可以保证动画过程不会因为卡顿而影响正常的时间进度(具体请自己体会,我就不细说了)。这种方法可能计算量比较大,涉及了取系统时间、乘除法(其他方法只有一次加法)。

当然一种折中的方案也可以,就是“累加小量 + 结果判断”。

敏锐的直觉告诉我,跟我的屏幕是高分辨率屏幕有关。

计算机屏幕通常是 72 dpi (dots per inch),我的高分屏是 96 dpi,这样一个像素看起来就会更小,于是 Windows 为了改善字小的问题,就有缩放的问题了。

右键点击桌面 > 显示

06.jpg

这是 Windows 的问题,Windows 想了缩放这么个想法,新的程序都要支持 DPI Aware 这个功能,否则就会被缩放。

Chrome 对这个东西的支持可以说非常好,而且还有很多特殊的属性可以使用,但是同时也代{过}{滤}理了某些问题。

我们在这条语句前后下个断点,单步执行,分析一下。

07.jpg

断点之后可以在下方的 Console 中执行语句,上下文就是当前断点处的上下文

08.jpg

为什么 $('anc').scrollTop++ 执行之后 $('anc').scrollTop 的值只增长了 0.80.81.25 倍正好就是 1,看来的确和 DPI 有关。

我们来 Bing 一下 Chrome scrollTop DPI。经过一段时间浏览我发现了这篇文章 https://bugs.chromium.org/p/chromium/issues/detail?id=224444

大概的意思就是说,Chrome 的滚动时会按滚动条正好滚动真实的 1px 为基本单位来滚动,但是 Chrome 的内容是按照 DPI 缩放过的,所以就会内容只滚动了 0.8px

也就是虽然执行的是 $('anc').scrollTop = $('anc').scrollTop + 1,+1 也的确是 +1 了,但是加完之后会在向 0.8 来进行舍入,所以每次只加了 0.8

设想一下,如果我的屏幕缩放达到了 150%,基本单位变成了 0.67,而 1 正好处于 0.671.33 的中间,如果缩放大于 150% 的话,每次就会移动 2 基本单位的长度。

解决方案

问题分析完了,说一下解决方案吧。

其实这就是我反感原来那种写法的原因,会带来某些未知的问题。

解决方案也就是,把判断次数改成判断结果。

    if (this.annrowcount >= this.annrows.length) {
        $('anc').scrollTop = 0;
        this.annrowcount = 1;
        this.annst = setTimeout(function() {
            ann.announcementScroll();
        }, this.anndelay);
    } else {
        // 这里我只是借用了原来的变量,正常应该把这个变量名改成 annStartTop 之类的
        this.anncount = $('anc').scrollTop;
        // 滚动 this.annrows[this.annrowcount] 像素
        this.announcementScrollnext(this.annrows[this.annrowcount]);
    }
}
;
ann.announcementScrollnext = function(time) {
    // 向下滚动 1 像素
    $('anc').scrollTop++;
    // 判断滚动差值
    if (($('anc').scrollTop - this.anncount) < time) {
        this.annst = setTimeout(function() {
            ann.announcementScrollnext(time);
        }, 10);
    } else {
        this.annrowcount++; 
        this.annst = setTimeout(function() {
            ann.announcementScroll();
        }, this.anndelay);
    }
}

直接在断点的时候执行上述语句,把函数替换掉就可以测试了。

09.jpg

测试成功!

继续改进

上面这种方案依旧不好

// 计算两个元素的 offsetTop 的差值
this.annrows[this.annrowcount] = this.annlis[i].offsetTop - lasttop;

一开始使用这句代码,就将导致一系列问题。

因为 offsetTop 是正常的数值(通常是整数),而 scrollTop 是以 0.8px 为单位的数值,所以可能会有累积误差,大约是每滚动 5 条差 1px,虽然比之前的小多了,但是问题还是存在的。

我们必须从根本上解决问题

function announcement() {
    var ann = new Object();
    ann.anndelay = 3000;
    ann.annst = 0;
    ann.annstop = 0;
    ann.annrowcount = 0;
    ann.annlis = $('anc').getElementsByTagName("li");
    ann.annrows = new Array();
    ann.announcementScroll = function() {
        if (this.annstop) {
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
            return;
        }
        if (!this.annst) {
            // 以下部分为初始化代码
            var lasttop = -1;
            for (i = 0; i < this.annlis.length; i++) {
                if (lasttop != this.annlis[i].offsetTop) {
                    // 这里不再用相邻两个元素的差值了,而使用和第一个元素的差值
                    this.annrows[this.annrowcount] = this.annlis[i].offsetTop - this.annlis[0].offsetTop;
                    this.annrowcount++;
                }
                lasttop = this.annlis[i].offsetTop;
                // 上面这一部分就是计算两个元素的 offsetTop 的差值
            }
            // 这里我也很反感,能用 <= 尽量不要用 ==,万一少写了一个等于号呢?
            // 万一一条公告都没有呢?
            if (this.annrows.length == 1) {
                $('an').onmouseover = $('an').onmouseout = null;
            } else {
                this.annrows[this.annrowcount] = this.annrows[1];
                $('ancl').innerHTML += $('ancl').innerHTML;
                this.annst = setTimeout(function() {
                    ann.announcementScroll();
                }, this.anndelay);
                $('an').onmouseover = function() {
                    ann.annstop = 1;
                }
                ;
                $('an').onmouseout = function() {
                    ann.annstop = 0;
                }
                ;
            }
            this.annrowcount = 1;
            return;
        }
        if (this.annrowcount >= this.annrows.length) {
            $('anc').scrollTop = 0;
            this.annrowcount = 1;
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
        } else {
            // 滚动到 this.annrows[this.annrowcount] 位置
            this.announcementScrollnext(this.annrows[this.annrowcount]);
        }
    }
    ;
    ann.announcementScrollnext = function(targetTop) {
        // 向下滚动 1 像素
        $('anc').scrollTop++;
        // 直接比较 scrollTop 和 targetTop
        if ($('anc').scrollTop < targetTop) {
            this.annst = setTimeout(function() {
                ann.announcementScrollnext(targetTop);
            }, 10);
        } else {
            this.annrowcount++; 
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
        }
    }
    ;
    ann.announcementScroll();
}

同样我们替换一下这个函数测试一下。

10.jpg

继续改进算法

此时,我又发现一个问题,如果屏幕缩放小于 100%(屏幕缩放不能小于 100%,但是 Ctrl + 鼠标滚轮的方式可以将 Chrome 内容缩放调整到小于 100%),Chrome 对这个单位采取的舍入是向下取整,那么这个方法就会出现问题,$('anc').scrollTop++ 这句话不会对 scrollTop 造成任何变化,因为滚动单位已经大于 1px 了。

原本的方法是计数,第一次修改是改成计算差值,第二次修改是直接验证我们想要的结果。这次我们就要改成根据初始位置和时间算中间位置了。

function announcement() {
    var ann = new Object();
    ann.anndelay = 3000;
    ann.annst = 0;
    ann.annstop = 0;
    ann.annrowcount = 0;
    ann.anncount = 0;
    ann.annScrollTopBegin = 0;
    ann.annlis = $('anc').getElementsByTagName("li");
    ann.annrows = new Array();
    ann.announcementScroll = function() {
        if (this.annstop) {
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
            return;
        }
        if (!this.annst) {
            // 以下部分为初始化代码
            var lasttop = -1;
            for (i = 0; i < this.annlis.length; i++) {
                if (lasttop != this.annlis[i].offsetTop) {
                    // 这里不再用相邻两个元素的差值了,而使用和第一个元素的差值
                    this.annrows[this.annrowcount] = this.annlis[i].offsetTop - this.annlis[0].offsetTop;
                    this.annrowcount++;
                }
                lasttop = this.annlis[i].offsetTop;
                // 上面这一部分就是计算两个元素的 offsetTop 的差值
            }
            // 这里我也很反感,能用 <= 尽量不要用 ==,万一少写了一个等于号呢?
            // 万一一条公告都没有呢?
            if (this.annrows.length == 1) {
                $('an').onmouseover = $('an').onmouseout = null;
            } else {
                this.annrows[this.annrowcount] = this.annrows[1];
                $('ancl').innerHTML += $('ancl').innerHTML;
                this.annst = setTimeout(function() {
                    ann.announcementScroll();
                }, this.anndelay);
                $('an').onmouseover = function() {
                    ann.annstop = 1;
                }
                ;
                $('an').onmouseout = function() {
                    ann.annstop = 0;
                }
                ;
            }
            this.annrowcount = 1;
            return;
        }
        if (this.annrowcount >= this.annrows.length) {
            $('anc').scrollTop = 0;
            this.annrowcount = 1;
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
        } else {
            this.anncount = 0;
            this.annScrollTopBegin = $('anc').scrollTop;
            // 滚动到 this.annrows[this.annrowcount] 位置
            this.announcementScrollnext(this.annrows[this.annrowcount]);
        }
    }
    ;
    ann.announcementScrollnext = function(targetTop) {
        // 直接滚动到目标位置
        $('anc').scrollTop = this.annScrollTopBegin + this.anncount;
        // 计数器 +1
        this.anncount++;
        // 直接比较 scrollTop 和 targetTop
        if ($('anc').scrollTop < targetTop) {
            this.annst = setTimeout(function() {
                ann.announcementScrollnext(targetTop);
            }, 10);
        } else {
            this.annrowcount++; 
            this.annst = setTimeout(function() {
                ann.announcementScroll();
            }, this.anndelay);
        }
    }
    ;
    ann.announcementScroll();
}

这个问题也解决了

其他

setTimeout、setInterval 的问题

还有另外一个需要注意的问题,Chrome 会在浏览器切换到后台之后停掉所有间隔小于 1s 的延时/循环。

参考:https://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs

不过本例中并没有什么影响。

显示全部公告

全部公告.png

免费评分

参与人数 12威望 +2 吾爱币 +24 热心值 +12 收起 理由
cunzher + 1 + 1 膜拜大佬
杨大善人 + 1 + 1 用心讨论,共获提升!
gomg007 + 1 + 1 用心讨论,共获提升!
ptlantu + 1 + 1 谢谢@Thanks!
Hmily + 2 + 10 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
初亦泽 + 3 + 1 我很赞同!
zyqdzyqd + 1 + 1 热心回复!
Zhang.J + 1 + 1 我很赞同!
吾爱丶小灰 + 2 + 1 谢谢@Thanks!
DE377DE477 + 1 我很赞同!
ubuntu + 3 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
likang + 1 好吧!这个都能出个详细的分析过程。

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Ganlv 发表于 2018-2-14 14:33
zhouvip666 发表于 2018-2-13 22:02
比如你调试JS,可是我看到你发的JS代码,无法看懂里面的意思。此时如果是你,你该怎么来解决?

多看两遍,这个真的没什么办法。

别人算法都写得很工整了,变量名也没混淆。这个就是简单的初中数学/物理知识。x = x_0 + v * t 这个简单的算法。

如果不是算法搞不清楚,而是执行逻辑不清楚,那就下断点,单步调试,跟着算法的运行流程走一遍。
 楼主| Ganlv 发表于 2018-2-11 15:09
Hmily 发表于 2018-2-11 09:45
@Ganlv 论坛用的是dicuz程序,代码:http://git.oschina.net/ComsenzDiscuz/DiscuzX ,可以直接查看源代码 ...

要是 GitHub 的话我就自己提交了,码云的话我还真懒得新建一个账号,H大代为提交吧,这点小东西根本不需要版权的。

点评

OK,我提交一下。  详情 回复 发表于 2018-2-11 15:22
陌宇轩 发表于 2018-2-9 21:56
Hmily 发表于 2018-2-9 22:30
牛,就喜欢你这种分析bug带解决方案的,我后天也上线验证下。
wind8961 发表于 2018-2-9 22:31
大牛就是大牛,遇到问题就回去分析原因,并想出对策
65302666 发表于 2018-2-9 22:46
膜拜G大,希望能多出点CE教程,顺带问下G大是做什么方面工作的
DUAN1839 发表于 2018-2-9 22:49 来自手机
虽然不懂,但是好牛逼啊,佩服。
 楼主| Ganlv 发表于 2018-2-9 23:01
65302666 发表于 2018-2-9 22:46
膜拜G大,希望能多出点CE教程,顺带问下G大是做什么方面工作的

在校生。计算机无关专业。
Zhang.J 发表于 2018-2-9 23:19

这个分析的很厉害,点赞
海盗小K 发表于 2018-2-9 23:20
Ganlv 发表于 2018-2-9 23:01
在校生。计算机无关专业。

厉害,计算机无关都已经5个精华了
萌萌哒的小白 发表于 2018-2-9 23:22
厉害!很给力!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-27 08:14

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表