倒计时—精确与服务器时间保持一致

一、背景

        在项目中,需要使用倒计时来提醒用户做出相应的操作,并且对倒计时的精准性有比较高的要求。一开始是页面直接获取服务器时间,在页面设置定时器【setInterval(function{…}, 1000);】,但发现各个页面从一开始加载出的时间就不一致,并且用户做出相应的操作时会导致定时器卡住,致使误差更大。

二、原因分析

     致使这种情况的大致原因如下:

  1. 网络通讯需要耗费一定的时间;(暂时没想到办法计算
  2. 页面加载需要一定时间,并且不同浏览器的加载时间也不一样;
  3. JS是单线程运行的,当有其他操作就会致使定时器卡住,等其他操作结束之后再执行,造成误差。

倒计时误差模型

 注:此模型中未考虑由于用户的操作引起定时器阻塞导致的时间误差。

 二、JS实现较为精准的倒计时

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>倒计时</title>
        <script>
            var startTime = new Date().getTime();    // 此时时间(用于计算线程占有,以及渲染引起的时间误差)
            var i = 0;
            while(i < 80000){    // 模拟页面加载(占用线程)
                ++ i;
            }
        </script>
        <style>
            .outerDiv {
                width: 880px;
                margin: 50px auto;
            }
            .countDown {
                text-align: center; 
                padding-top: 10%; 
                padding-bottom: 5%; 
                line-height: 80px; 
                font-size: 46px; 
                color: #333;
            }
            .countDown i { 
                color: #fff; 
                display: inline-block; 
                border-radius: 10px; 
                width: 68px;
                height: 80px;
                background: #ff9f09; 
                margin: 0 6px;
                padding-right: 10px;
            }
        </style>
    </head>
    <body>
        <div class="outerDiv">
            <div id="timeStrDiv" class="countDown">
			
            </div>
        </div>
    </body>
    <script>
        window.onload = function(){
            countDown();
        };
		
        var timer = null;
        var showStr = "";
        var showDiv = document.getElementById("timeStrDiv");
        function countDown(times){
            if (times == null || times == '') {
                times = 24321;    // 从服务器获取得倒计时(单位为毫秒)
                times -= (new Date().getTime() - startTime);    // 获取倒计时开始时间(减去页面加载时间)
            }
            if (timer != null) {
                clearTimeout(timer);
            }
			
            var interval = 1000;    // 设定倒计时标准间隔差
            startTime = new Date().getTime();    // 此时时间(用于计算线程占有,以及渲染引起的时间误差)
            var count = 0;    // 标记执行次数
			
            // 判断是否需要倒计时
            if (Math.floor(times / interval) > 0) {
                timer = setTimeout(countDownStart, interval);
            } else {
                showStr = "倒计时:<i>00</i>天<i>00</i>小时<i>00</i>分钟<i>00</i>秒";
                showDiv.innerHTML = showStr;
            }
			
            /** 真正的倒计时  */
            function countDownStart(){
                count++;
				
                var j = 0;
                while(j < (count * 8000000)){    // 模拟逻辑代码(占用线程)
                    ++ j;
                }
				
                showStr = formatTime("倒计时:<i>DD</i>天<i>HH</i>小时<i>MM</i>分钟<i>ss</i>秒", times);
                showDiv.innerHTML = showStr;
				
                var offset = new Date().getTime() - (startTime + count * interval);   // 计算误差
                if (offset > interval) {    // 若误差超过间隔时间,则立即校正(会出现跳秒的情况)
                    times -= Math.floor(offset / interval) * interval;
                    count += Math.floor(offset / interval);
                    offset = offset % interval;
                }
                var nextTime = interval - offset;    // 标准时间间隔减去此次的误差,为下一次执行的时间
				
                if (Math.floor(times / interval) <= 0) {
                    clearTimeout(timer);
                    showStr = "倒计时:<i>00</i>天<i>00</i>小时<i>00</i>分钟<i>00</i>秒";
                    showDiv.innerHTML = showStr;
                } else {
                    times -= interval;
                    timer = setTimeout(countDownStart, nextTime);
                }
				
                console.log("执行次数:" + count + ", 误差:" + offset + "ms, 下一次执行:" + nextTime + "ms之后, 所剩时间:" + times + "ms");
            }
        }
		
        /** 格式化时间形式 */
        function formatTime(fmt, time){
            fmt = fmt.toUpperCase();
            var day = formatTimeField(Math.floor(time / (1000 * 60 * 60 * 24)));
            var hour = formatTimeField(Math.floor(time / (1000 * 60 * 60)) - (day * 24));
            var minute = formatTimeField(Math.floor(time / (1000 * 60)) - (day * 24 * 60) - (hour * 60));
            var second = formatTimeField(Math.floor(time / 1000) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60));
            var timeField = {
                "D+": day,
                "H+": hour,
                "M+": minute,
                "S+": second
            };
            for(var field in timeField){
                if(new RegExp("(" + field + ")").test(fmt)){
                    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length===1) ? (timeField[field]) : (("00" + timeField[field]).substr(("" + timeField[field]).length )));
                }
            }
            return fmt;
        }
			
        /** 格式化时间分量 */
        function formatTimeField(number){
            if (number <= 9) {
                number = "0" + number;
            }
            return number;
        }
    </script>
</html>
发布了47 篇原创文章 · 获赞 16 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/zorro_jin/article/details/86018487
今日推荐