使用Canvas制作时钟动画

  复习Javascript到Canvas的知识点,看到一个使用Canvas绘制的静态时钟例子,便想将其变成动态显示系统时间的时钟动画。另外再配上数字显示的时钟,一个小的时钟模块的诞生了!目前的界面还比较粗糙,只有简单的界面和动画效果。

演示观看地址:http://htmlpreview.github.io/?https://github.com/omocc/Demo/blob/master/%E6%97%B6%E9%92%9Fdemo/clock.html

  这个时钟包括两个部分,动画圆盘时钟和数字时钟。首先是使用超时调用setTimeout()方法做一个循环动画的效果以显示时间。先看数字时钟的代码,比较简单,圆盘时钟的做法也是模仿数字时钟做的。

var ntimeoutId = setTimeout(ntimeOut,0);
function ntimeOut() {
    clearTimeout(ntimeoutId);
    var now = new Date();
    var hours = now.getHours().toString(),
        minutes = now.getMinutes().toString(),
        seconds = now.getSeconds().toString();
    var time = hours+" : "+minutes+" : "+seconds;
    var timediv = document.getElementById("time");
    timediv.innerHTML = time;
    ntimeoutId = setTimeout(ntimeOut, 1000);
}

  1、首先是设定一个超时调用以首次调用方法来显示系统当前时间,时间之所以设置为0,是为了没有延迟地显示时间。

  2、每次循环之前清除前一次的超时调用(为什么要这样做,我也还不清楚???可能是为了内存性能相关,注释了以后程序也能正常执行。)

  3、取得当前系统时间,并按照一定的字符串格式保存。

  4、为了动态显示在页面中,在Html页面中定义了一个空的div元素,以存放该时间字符串。使用DOM操作将其添加到div元素中即可动态显示。

  5、通过不停循环超时调用,就可以动态显示数字时钟了, 同过开发者工具也可以看到div元素里的动态变化。

  接下来就是制作圆盘时钟动画,圆盘和数值的刻画都比较简单,只要使用context.arc()方法和context.fillText()方法即可。下面是其源代码:

     context.beginPath();
        context.restore();
        context.translate(0,0);
        context.clearRect(0,0,300,300);

        //绘制时钟内外边框
        context.arc(150,150,149,0,2 * Math.PI,false);
        context.moveTo(295,150);
        context.arc(150,150,145,0,2 * Math.PI,false);

        context.font = "bold 18px Arial";
        context.textAlign = "center";

        //绘制时钟表盘数值
        context.fillText("12",150,25);
        context.fillText("3",285,150);
        context.fillText("6",150,290);
        context.fillText("9",15,150);
        context.fillText("1",215,45);
        context.fillText("2",265,95);
        context.fillText("4",265,225);
        context.fillText("7",95,275);
        context.fillText("5",215,275);
        context.fillText("8",35,225);
        context.fillText("10",35,95);
        context.fillText("11",75,45);

        context.stroke();
        context.closePath();

  接下来就是指针的绘制了,指针的绘制中参考高程中的做法,使用变换操作context.translate()方法改变原点,再绘制路径成为指针会方便很多。另一个绘制指针的难点是弧度的计算,当然这就是数学问题了。下面先看源代码:

     //绘制指针
        context.save();
        context.translate(150,150);

        //时针
        context.moveTo(0,0);
        hour(hours);

        function hour(thour) {
            context.save();
            var newhour = 0;
            if(thour>12) {
                newhour = thour-12;
            } else {
                newhour = thour;
            }
            context.rotate((2*Math.PI/12)*newhour);
            context.lineTo(0,-80);
            context.restore();
        }

        //分针
        context.moveTo(0,0);
        minute(minutes);

        function minute(tminute) {
            context.save();
            context.rotate((2*Math.PI/12)*tminute/5);
            context.lineTo(0,-110);
            context.restore();
        }

        //秒针
        context.moveTo(0,0);
        second(seconds);

        function second(tsecond) {
            context.save();
            context.fillStyle = "#fff";
            context.rotate((2*Math.PI/12)*tsecond/5);
            context.lineTo(0,-120);
            context.restore();
        }
        
        context.stroke();

  在绘制指针中,每种指针都使用了函数来改变每次指针绘制的弧度来实现指针转动的动画效果。对于时针,则将二十四小时制转化为十二小时制,每次转动30°即可。分针和秒针则是转为0到11进行转动,简单的数学问题,相信大家都是比我厉害的,当时我还纠结了一阵子。在每个函数中都有使用context.save()方法和context.restore()方法,是为了保存和复原初始化时的路径,不然指针都要跑偏啦。

  所有工作基本准备就绪了,接下来只要将钟盘和指针都放在超时调用中即可,但仍然有些问题需要注意的,比如说钟盘和指针的原点设置不同,要注意使用保存和复原来还原初始化时候的路径,不然钟盘和指针都要跑偏了。另外要注意的是,使用Canvas制作动画,每次的动画循环都是要清空画布重新绘制,不然指针一直转转转,转成一朵花的样子。

  完整的圆盘时钟代码如下:

//显示指针时间
var drawing = document.getElementById("drawing");
if(drawing.getContext) {
    var context = drawing.getContext("2d");

    var rtimeoutId = setTimeout(roudClock,0);
    function roudClock() {
        clearTimeout(rtimeoutId);

        context.beginPath();
        context.restore();
        context.translate(0,0);
        context.clearRect(0,0,300,300);

        //绘制时钟内外边框
                context.arc(150,150,149,0,2 * Math.PI,false);
        context.moveTo(295,150);
        context.arc(150,150,145,0,2 * Math.PI,false);

        context.font = "bold 18px Arial";
        context.textAlign = "center";

        //绘制时钟表盘数值
                context.fillText("12",150,25);
        context.fillText("3",285,150);
        context.fillText("6",150,290);
        context.fillText("9",15,150);
        context.fillText("1",215,45);
        context.fillText("2",265,95);
        context.fillText("4",265,225);
        context.fillText("7",95,275);
        context.fillText("5",215,275);
        context.fillText("8",35,225);
        context.fillText("10",35,95);
        context.fillText("11",75,45);

        context.stroke();
        context.closePath();
        
        var now = new Date();
        var hours = now.getHours(),
            minutes = now.getMinutes(),
            seconds = now.getSeconds();

        //绘制指针
        context.save();
        context.translate(150,150);

        //时针
                context.moveTo(0,0);
        hour(hours);

        function hour(thour) {
            context.save();
            var newhour = 0;
            if(thour>12) {
                newhour = thour-12;
            } else {
                newhour = thour;
            }
            context.rotate((2*Math.PI/12)*newhour);
                        context.lineTo(0,-80);
            context.restore();
        }

        //分针
           context.moveTo(0,0);
        minute(minutes);

        function minute(tminute) {
            context.save();
            context.rotate((2*Math.PI/12)*tminute/5);
            context.lineTo(0,-110);
            context.restore();
        }

        //秒针
                context.moveTo(0,0);
        second(seconds);

        function second(tsecond) {
            context.save();
            context.fillStyle = "#fff";
            context.rotate((2*Math.PI/12)*tsecond/5);
            context.lineTo(0,-120);
            context.restore();
        }
        
        context.stroke();
        context.restore();
        context.translate(0,0);
        context.save();
        rtimeoutId = setTimeout(roudClock,1000);
    }    
}

  最后总结我在这次Demo的练习中遇到的几点问题:

  1、画布重绘问题

  在指针动画循环的时候,前一个路径都没有方法清除,造成每一次循环都留下印记。尝试了小范围地使用clearRect()方法,结果发现只能在范围内清除了表盘和数字的内容,指针依然会留下痕迹。后来通过搜索,得到的答案是使用Canvas制作动画一定是要进行重绘的,在新绘制内容前要清空画布内容,每一次的动画变化都要清空一次。重绘的方法可以参考一下链接,我使用的是clearRect()方法清空整个画布。

参考方法链接:http://blog.csdn.net/u010484625/article/details/46046217

  2、save()方法和restore()方法的使用

  因为我的钟盘和指针的原点设定不同,所以在进行重绘后,钟盘的原点会改变为指针的原点,因此要利用save()方法和restore()方法改变。除了这个地方也有其他一些地方需要用到,适合操作以后改变了设定,但后续操作需要用原设定的情况。注意的是这两个方法只会保存和恢复设定,而不是内容。

  3、translate()方法的使用

  translate()方法是属于变换操作中的,可改变画布的原点,默认画布的原点在画布的左上角。使用这个方法可以更轻易地绘制指针的路径,当然还有一些其他需要用的地方,我还没有接触到。

  4、setTimeout()方法的使用

  使用超时调用是比间歇调用更好的方法,使用超时调用可以模拟间歇调用。通过在超时调用传入的函数中再添加超时调用就可以很好地模仿循环。还可以在函数中根据一些条件限定循环的次数和时间。

  5、对应当前时间,弧度的使用方法

  这就是数学问题,我在这个问题纠结了好一会儿,没有拐过弯来,明白其中的原理就不是技术上的难题了。

  6、对于时间重复取得和重复使用方法的问题

  对于代码中还存在有重复代码的情况,我还没有想到更好的方法减少冗余。例如在获取时间上,在两个超时调用中均有重复定义,但若是把他们放在全局中,则没有了动画效果,只是显示加载完成后的那个静态时间。另外的是指针的函数设定中有重复的部分,是否可以合为一个函数方法再进行调用呢。

  欢迎大家提出想法,对于不足的地方提出建议,一起来交流。

 

posted @ 2017-10-15 12:47  omoc  阅读(1025)  评论(0编辑  收藏  举报