阅读 394

操作canvas:实时时钟制作

微信截图_20210706104825.png

这是我参与新手入门的第一篇文章,第一次写这种技术类的文章一开始我犹豫了很久也不知道写些什么怎么写,但当我把它当成一种知识分享和自己思考的过程进行记录变得简单了许多。所以有什么不足的地方可以在评论区发表你的意见,如果你觉得我写的还不错可以点一下赞,这是对于我是非常好的鼓励,谢谢。

单纯的使用数值的形式显示当前时间太过于单调,所以这里我想制作一个时钟的样子进行显示当前时间再通过JavaScript让它动起来,上图就是时钟的大概的一个样子,下面就开始展示吧。

一、画制时钟

1.绘制钟盘和钟盘圆心

首先我们需要了解canvas画制图片的原理,这里想必大家小时候电脑课上应该都玩过小海龟那个画图工具,其实这里的canvas与小海龟其实是很相似的。 这里我们首先需要在html部分添加一个<canvas></canvas>标签,通过添加css样式设置一下时钟的背景色和位置,时钟的外壳就出来了。

    <style>
        /* 时钟的样式 */
        .clock {
            display: block;
            margin: 100px auto;
            border-radius: 20px;
            background: #ccc;
        }
    </style>
    
    <!-- 画板 -->
    <div class="clock">
        <canvas width="400px" height="400px"></canvas>
    </div>
复制代码

微信截图_20210706121127.png
html和css部分就弄好了,下面就要通过JavaScript操作canvas了,这里我们先要定义一下几个要使用到的变量:

钟体的半径(clockWidth)、时针半径(r_hours)、分诊半径(r_minute)、秒针半径(r_second)

这里我们要用到JavaScript操作canvas标签,那肯定少不了获取DOM这一步操作,首先我们获取一下canvas标签,然后为获取到的画板DOM对象指定一下绘图环境。
var canvas = document.querySelector("canvas"); var clock = canvas.getContext('2d');

现在我们已经获取到绘图的返回环境了这个对象会导出相应的绘制API,这里我们先画制钟盘和时钟的圆心,在绘制线条的时候都要通过clock.beginPath()函数创建一条新的线条,然后再通过
clock.arc(x,y,r,sAngle,eAngle,counterclockwise)函数画制时钟的圆环

x:数值(位于画板的x坐标)
y:数值(位于画板的y坐标)
r:数值(绘制圆弧的半径)
sAngle:弧度(绘制圆弧的起始弧度)
eAngle:弧度(绘制圆弧的结束弧度)
counterclockwise:布尔值(绘制圆弧是从顺时针还是逆时针)

arc.gif

这里需要注意的是角度值,只有搞定了这个角度画一个圆毕本上是水到渠成,首先我们需要了解一下π,这个大家对它都不陌生吧,他就是一个半圆的周长与直径的比值也就是180°,所以这里我们可以通过Math.PI获取一下π,因为我们要画制360°的圆,所以还要为获取的π乘二。绘制完两个圆后通过clock.fillStyle属性设置颜色然后调用clock.fill()函数对刚才画制的圆进行上色,整体代码如下:

// 画时钟的圆盘和钟盘的圆心
clock.beginPath();
clock.arc(200,200,clockWidth,0,2*Math.PI,false);
clock.fillStyle = "#fff";
clock.fill();
clock.beginPath();
clock.arc(200,200,7,0,2*Math.PI,false);
clock.fillStyle = '#000';
clock.fill();
复制代码

效果图如下:

微信截图_20210706145740.png

2.画制钟盘刻度

这里需要通过clock.moveTo(x,y)函数确定直线的起始点x、y是在画板中的坐标不能超出画板的大小不然显示不出来,然后我么通过clock.lineTo(x,y)确定直线的结束位置,绘制完后是不是没有效果呢?这里要为直线上色才可以显示出来,这里可以为clock.strokeStyle属性赋值设置线条的颜色然后通过clock.closePath()为线条上色。 现在我们学会了如何画制线条,下一步就要思考如何把刻度画制到圆上呢,首先我们先要求得线条在圆上的初始位置,因为正弦是对边比斜边而余弦就是邻边对斜边,现在我们的时钟半径是已知的,所以我们可以先求得该刻度下的正弦和余弦再乘以斜边(时钟半径)就可得到一个横坐标和纵坐标的长度。然后再为这两个长度加上圆心的横坐标和纵坐标,就可以得到刻度的初始的x坐标和y坐标,设置线条的结束坐标为斜边减去一定的长度就可以得到绘制刻度的结束x坐标和y坐标,也是刻度相应的长度。

微信截图_20210706145740.png

前面我们说了2π是360°,那么这里我们需要画制60个刻度也就是每6°画制一条刻度,这里通过一个for循环计算每个刻度的角度,然后进行画制,因为后面需要大量对角度的计算所以我这里定义了一个变量pivar pi = Math.PI*2/360,它的值为1°。这里在绘制刻度的时候有一个细节小时的刻度和分秒的刻度需要区分开来,所以我为其添加了一个判断通过改变线条粗细进行区分,为clock.lineWidth赋值就可修改线条的粗细。这里因为画制线条的代码量比较繁杂和重复性较高所以我给他封装成一个dwLine函数了,减少了很多没有必要的重复也提高了工作效率。

// 画制线条函数
/* 
    start1: 起始横坐标
    start2:起始纵坐标
    end1:结束横坐标
    end2:结束纵坐标
    color:设置线条样式
*/
function dwLine(start1,start2,end1,end2,color="#000"){
    clock.beginPath();
    clock.moveTo(start1,start2);
    clock.lineTo(end1,end2);
    clock.strokeStyle = color;
    clock.stroke();
    clock.closePath();
}

// 画制时钟刻度
// 定义1°
var pi = Math.PI*2/360;

for(var i=1;i<=60;i++){
    if(i%5==0){
        clock.lineWidth = 3;
    }
    else {
        clock.lineWidth = 1;
    }
    var x = Math.cos(pi*6*i);
    var y = Math.sin(pi*6*i);
    dwLine(200+r_clock*x,200+r_clock*y,200+(r_clock-10)*x,200+(r_clock-10)*y)
}
复制代码

微信截图_20210706160449.png

3.绘制时钟钟点数字

绘制时钟钟点数字,画制文字通过clock.fillText()函数画制的,和画制时钟刻度的逻辑相同的也是要使用到正弦和余弦求横坐标和纵坐标,这里因为小时的刻度和时钟钟点数字的角度是重合的,所以我们为了节省代码在绘制时钟刻度循环中的判断条件内加多一条时钟钟点数字绘制的代码,绘制出来的数字没有居中对齐的,所以我们还要给它居中一下,通过clock.textAlign = 'center'clock.textBaseline = 'middle'为字符设置水平居中和垂直居中,通过clock.font = 'bold 17px 微软雅黑'可以设置字体样式。这里还有一点需要注意的是,因为画制刻度样子基本上是一样的,不需要明显的区分,但是这里画的数字就有明显的区别。因为角度是从第四象限开始一直顺时针计算下去的,所以12就没有在他所在的位置,所以这里需要在计算正弦和余弦的时候给他减去1/2π也就是90°。

// 定义1°
var pi = Math.PI*2/360;

for(var i=1;i<=60;i++){
    var x = Math.cos(pi*6*i-Math.PI/2);
    var y = Math.sin(pi*6*i-Math.PI/2);
    if(i%5==0){
        clock.lineWidth = 3;
        clock.textAlign = 'center';
        clock.textBaseline = 'middle';
        clock.font = 'bold 17px 微软雅黑';
        clock.fillText(i/5,200+145*x,200+145*y);
    }
    else {
        clock.lineWidth = 1;
    } 
    dwLine(200+r_clock*x,200+r_clock*y,200+(r_clock-10)*x,200+(r_clock-10)*y)
}
复制代码

可以看到图1是没有减1/2π的效果,图2是减去1/2π后的效果:
1.微信截图_20210706164258.png

2.微信截图_20210706164758.png

二.让时钟动起来

这里明明还没有画时分秒的指针为什么说时钟可以动起来了呢?我的思路是这样的,竟然如果已经得到了一个时间的,那么就可以计算出多少时或多少分所对应角度,所以就可以换一种思路不用先画制大概时针的样子然后在想它怎么动。首先这里需要获得当前的时间中的时分秒,这里因为小时是24小时制的而我们的闹钟是12小时制的,所以这里要给他进行一个判断。

var date = new Date();
var h = date.getHours();
if(h>12){
    h-=12;
}
var m = date.getMinutes();
var s = date.getSeconds();
复制代码

然后就可以绘制指针了,这里需要注意的是与绘制刻度不同这里绘制的初始点是圆心。绘制完指针后你会发现指针的线条太过于方正,可以为线条设置为圆形线帽让它变得柔和一些clock.lineCap = 'round'

// 定义1°
var pi = Math.PI*2/360;
clock.lineWidth = 7;
var x = Math.cos(pi*h*30-Math.PI/2);
var y = Math.sin(pi*h*30-Math.PI/2);
dwLine(200,200,200+r_hours*x,200+r_hours*y,'#000');

clock.lineWidth = 4;
var x = Math.cos(pi*m*6-Math.PI/2);
var y = Math.sin(pi*m*6-Math.PI/2);
dwLine(200,200,200+r_minute*x,200+r_minute*y,'rgb(151, 63, 0)');

clock.lineWidth = 2;
var x = Math.cos(pi*s*6-Math.PI/2);
var y = Math.sin(pi*s*6-Math.PI/2);
dwLine(200,200,200+r_second*x,200+r_second*y,'red');
复制代码

微信截图_20210706172755.png

现在时钟的样子已经出来了,每当你刷新一次网页,秒针就跟着走动一次但只是走一次就不会再动了,所以我们还要为绘制指针和获取当前时间的代码设置一个计时器。之前遇到一个小bug就是计时器每执行一次画制新指针,旧的指针仍然存在,如果遇到这个问题就重新获取一下画板可以实现清空画板的效果var clock = canvas.getContext('2d');,但我今天写文案的时候发现这个问题消失了,最终的效果如下:

GIF.gif

最终代码如下:

HTML部分

<style>
    /* 时钟的样式 */
    canvas {
        display: block;
        margin: 100px auto;
        border-radius: 20px;
        background: #ccc;
    }
</style>

<!-- 画板 -->
<div class="clock">
    <canvas width="400px" height="400px"></canvas>
</div>
复制代码

JS部分

// 基本变量
// 钟盘大小
var r_clock = 170;
// 时针长度:时分秒
var r_hours = 70;
var r_minute = 100;
var r_second = 130;
// 定义1°
var pi = Math.PI*2/360;
// 获取画板
var canvas = document.querySelector("canvas");
var clock = canvas.getContext('2d');

// 画线条函数
function dwLine(start1,start2,end1,end2,color="#000"){
    clock.beginPath();
    clock.moveTo(start1,start2);
    clock.lineTo(end1,end2);
    clock.strokeStyle = color;
    clock.stroke();
    clock.closePath();
    clock.lineCap = 'round';
}





function time() {
    // 绘制时钟的圆盘和钟盘的圆心
    clock.beginPath();
    clock.arc(200,200,r_clock,0,2*Math.PI,false);
    clock.fillStyle = "#fff";
    clock.fill();
    clock.beginPath();
    clock.arc(200,200,7,0,2*Math.PI,false);
    clock.fillStyle = '#000';
    clock.fill();


    // 绘制时钟刻度
    clock.strokeStyle = "#000";
    for(var i=1;i<=60;i++){
        var x = Math.cos(pi*6*i-Math.PI/2);
        var y = Math.sin(pi*6*i-Math.PI/2);
        if(i%5==0){
            clock.lineWidth = 3;
            // 绘制时钟数字
            clock.textAlign = 'center';
            clock.textBaseline = 'middle';
            clock.font = 'bold 17px 微软雅黑';
            clock.fillText(i/5,200+145*x,200+145*y);
        }
        else {
            clock.lineWidth = 1;
        } 
        dwLine(200+r_clock*x,200+r_clock*y,200+(r_clock-10)*x,200+(r_clock-10)*y)
    }
    
    // 获取当前的时分秒
    var date = new Date();
    var h = date.getHours();
    if(h>12){
        h-=12;
    }
    var m = date.getMinutes();
    var s = date.getSeconds();

    // 绘制时针
    clock.lineWidth = 7;
    var x = Math.cos(pi*h*30-Math.PI/2);
    var y = Math.sin(pi*h*30-Math.PI/2);
    dwLine(200,200,200+r_hours*x,200+r_hours*y,'#000');

    // 绘制分针
    clock.lineWidth = 4;
    var x = Math.cos(pi*m*6-Math.PI/2);
    var y = Math.sin(pi*m*6-Math.PI/2);
    dwLine(200,200,200+r_minute*x,200+r_minute*y,'rgb(151, 63, 0)');

    // 绘制秒针
    clock.lineWidth = 2;
    var x = Math.cos(pi*s*6-Math.PI/2);
    var y = Math.sin(pi*s*6-Math.PI/2);
    dwLine(200,200,200+r_second*x,200+r_second*y,'red');
}

time();

setInterval(function(){
    time();
},1000);  
复制代码
文章分类
前端
文章标签