JS 前端倒计时时间不准确如何解决?

2021-08-09 09:18:40 +08:00
 CSGO

我在写一个简单的页面,有 3 个倒计时,在别人的帮助下,成功实现了功能,但是发现 js 代码在不同浏览器下,倒计时都有误差,大约每 60 秒就会慢 1 秒钟左右,有没什么办法修复它?

function setNumber(elem, num) {
    elem.innerText = num.toString().padStart(2, '0');
}

function countdown(elem, init) {
    setNumber(elem, init);
    return setInterval(() => {
        let num = Number(elem.innerText);
        let next = num - 1;
        if (next < 0) next = init;
        setNumber(elem, next);
    }, 1000);
}
function init() {
    var timeList = [
        { 'name': 'explode-id', 'node': 'wifi-explode', 'time': 60 },
        { 'name': 'nokit-id', 'node': 'wifi-nokit', 'time': 30 },
        { 'name': 'ownkit-id', 'node': 'wifi-ownkit', 'time': 35 }
    ];
    timeList.forEach(function (item) {
        clearInterval(parseInt(document.getElementById(item['name']).innerHTML));
        document.getElementById(item['name']).innerHTML = countdown(document.getElementsByClassName(item['node'])[0], item['time']);
    });
}
let wifiClick = document.getElementsByClassName('wifi-click')[0];
init();
wifiClick.addEventListener('click', () => {
    init();
})
6866 次点击
所在节点    问与答
54 条回复
matepi
2021-08-09 16:49:02 +08:00
setTimeout,里面不是做自增自减

而是应该做目标差

即便不是 js,是多任务的 interval 从来就是不准的,早年就有由于晶振原因,最小时间片 56 还是 58ms 之说。以延时 1000ms,也只是获得了最接近的时间。

因此当年写反复延时,都得知道要靠做差法,而不是每次延时。

作差法也有问题,就是用来做显示的事件触发化,有低概率在同一秒内,事件如显示的 update 这次不会触发。
netwjx
2021-08-09 17:14:22 +08:00
UI 编程永远都会有类似的毫秒级误差问题, 不仅仅是浏览器端, android, ios 都一样

因为不能允许多线程共同操作 UI 资源, 会线程冲突


setInterval 每次的误差应该是毫秒级, 60 次就能累积到 1s 还是有点高了
更进一步的办法类似 41 楼说 算目标时间差

这是动画 /游戏开发的技巧, 确保在不同的设备上实际时间一致
cyrbuzz
2021-08-09 17:53:53 +08:00
@Biwood

还是有点疑问,setTimeout 设置 0 会有最小 4ms 的延迟是清清楚楚在文档里写的,非活跃的标签在各个浏览器下对定时器也有节流在文档里也可以清楚看到,raf 非活跃不会执行。

我的疑问是仅从楼主代码来看,1000ms 这个为什么会有延迟,楼主的代码本身没有会阻塞很长时间的代码,楼主说 60 秒会慢 1 秒,也就是每次大概都会有 16.66ms 的延迟,为什么会有如此大的延迟?楼主定时器的内的代码:

```
let num = Number(elem.innerText);
let next = num - 1;
if (next < 0) next = init;
elem.innerText = num.toString().padStart(2, '0');
```

他怎么看都花不了 16ms 。
Quarter
2021-08-09 17:59:54 +08:00
倒计时可以不用手动—,可以记录开始的时间戳,然后定时执行此时的时间戳和开始的时间戳的差值对比倒计时显示结果,这样应该可以避免你说的时间误差问题🦖
Quarter
2021-08-09 18:03:22 +08:00
另外,也可以不用 setInterval,用 requestAnimationFrame/cancel AnimationFrame 的 API,当然,计算次数会变多,但是更精准一些
Biwood
2021-08-09 18:16:25 +08:00
@cyrbuzz
眼睛看肯定是看不出来的,具体延迟多少不仅取决于代码逻辑,还跟计算机本身的运行状态等因素也有关系,这是很难确定的
des
2021-08-09 18:48:17 +08:00
@cyrbuzz 你可以算一下 60hz 下,一帧平均能分到多长时间,看看这个时间有没有很眼熟?
lysS
2021-08-09 20:11:19 +08:00
在后台循环打印 🐸

🐶
CSGO
2021-08-09 20:45:49 +08:00
@cyrbuzz 当电脑比较卡的时候,就是 CPU 占用率高的时候延时越高。
crclz
2021-08-09 22:56:17 +08:00
我记得上学的时候做过一个定时器。老师想要全班考试的时候能看到倒计时,有紧迫感。
用的是 Thread.Sleep,结果第一版出来,考个理综,我们班比其他班多 5 分钟时间,哈哈哈。
kyuuseiryuu
2021-08-09 23:43:19 +08:00
setTimeout 和 setInterval 第二个参数的意思是回调函数最快被调用的时间限制。
cyrbuzz
2021-08-10 10:34:52 +08:00
@Biwood
@CSGO
@des

谢谢各位大佬的回复,感觉大佬们的回复没有解决我的疑问= =...可能是我理解力不够,我承认 CPU 处理不过来内容,掉帧情况等等都会有误差产生,否则也不会衍生出 raf, ric 这些按帧回调,空闲回调的 API 了。

但我还是不相信楼主这种代码都会产生 1s 的误差,所以做了一下测试,因为楼主没有贴 HTML 部分代码稍微还原了一下:

```
<body>
<div class="wifi-click">
</div>

<div id="explode-id">

</div>

<div id="nokit-id">

</div>

<div id="ownkit-id">

</div>

<div class="wifi-explode">

</div>


<div class="wifi-nokit">

</div>


<div class="wifi-ownkit">

</div>
</body>
<script>
function setNumber(elem, num) {
elem.innerText = `${num.toString().padStart(2, '0')}<br>${new Date()}`;
}

function countdown(elem, init) {
setNumber(elem, init);
return setInterval(() => {
let num = Number(elem.innerText.split('<br>')[0]);
let next = num - 1;
if (next < 0) next = init;
setNumber(elem, next);
}, 1000);
}
function init() {
var timeList = [
{ 'name': 'explode-id', 'node': 'wifi-explode', 'time': 60 },
{ 'name': 'nokit-id', 'node': 'wifi-nokit', 'time': 30 },
{ 'name': 'ownkit-id', 'node': 'wifi-ownkit', 'time': 35 }
];
timeList.forEach(function (item) {
clearInterval(parseInt(document.getElementById(item['name']).innerHTML));
document.getElementById(item['name']).innerHTML = countdown(document.getElementsByClassName(item['node'])[0], item['time']);
});
}
let wifiClick = document.getElementsByClassName('wifi-click')[0];
init();
wifiClick.addEventListener('click', () => {
init();
})

wifiClick.innerHTML = new Date()
</script>
```

在楼主代码基础上加了 new Date()方便观察,一开始这段代码确实会每分钟都比 new Date 慢一秒,此时我的电脑都处于 CPU 占用率 0~1%之间,记录 Performance 也没有发现异常。

直到注意到楼主的定时器代码里:

```
let next = num - 1;
if (next < 0) next = init;
```

当 next 小于 0 时才重置,这样就导致本应该 1 分钟 0~59,1~60 的循环变成了 0~60,多了一次....= =。
Kinnice
2021-08-10 15:42:15 +08:00
@cyrbuzz 破案
CSGO
2021-08-10 15:48:04 +08:00
@cyrbuzz 啊,这这样啊。。。我有放在网络上: https://csgo.link/web/c4time

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/794514

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX