canvas实现时钟特效

本文分享了作者在阅读《JavaScript高级程序设计》时,通过实现动画和canvas技术,亲手制作了一个闹钟特效,包括刻度、时针、分针的绘制与定时更新。同时介绍了两个额外的时钟特效实现思路和代码细节。
摘要由CSDN通过智能技术生成

这两天在重读《JavaScript 高级程序设计第 4 版》,感觉有了一些新的认识和发现,收获还是挺多的。今天刚刚好阅读到动画和canvas这部分,书中给的一个小示例是一个闹钟

image.png

当时觉得挺有意思的,就想着自己着手也实现一个类似的特效(本来准备写论文的,前两天感冒了 ,有点不舒服,进度有点迟缓,不过这不影响写代码())

效果图

clock.gif

实现思路

闹钟的实现思路也比较容易,简单介绍一下思路。先创建一个大圆(闹钟的外层),闹钟的刻度是单个线条,实现单个线条也比较简单,主要就是moveTo和lineTo的使用,结合一些基础的数学知识,获取相应的坐标即可;接着是时针和分针的编写,原理和刻度一样。至于,让指针走起来,我这里使用的是setInterval定时器,每隔一秒刷新一次时间,并且清空画布,重新绘制。文字没有什么好说的,就是简单的api调用,颜色的话,还是有些地方需要小心的,比如怎么让不同的内容拥有不同的颜色,这里不多解释了,想要了解的小伙伴可以点击在同一个 canvas 元素中绘制不同颜色的图形查看。

代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>

<script>
    window.onload = function () {
        //写逻辑
        let canvas = document.getElementById('canvas');
        let cxt = canvas.getContext('2d');

        const width = 400;
        const height = 400;

        canvas.width = width;
        canvas.height = height;

        let x;
        let y;

        let hour;
        let minute;
        let second;

        cxt.font = "bold 14px Arial";
        cxt.textAlign = "center";
        cxt.textBaseLine = "middle";
        

        setInterval(() => {

            cxt.clearRect(0, 0, width, height);
             
            // 绘制闹钟外壳
            cxt.beginPath();
            cxt.fillStyle = "#d9ddd8";
            cxt.strokeStyle = "#e5d4a8";
            cxt.lineWidth = 3;
            cxt.arc(200, 200, 200, 0, 2 * Math.PI, false);
            cxt.stroke();
            cxt.closePath();

            cxt.beginPath();
            cxt.strokeStyle = "#84c2b3";
            
            // 绘制小时刻度并添加文字
            for (let i = 3; i >= -8; i--) {
                cxt.lineWidth = 4;
                x = 200 * Math.sin(Math.PI / 6 * i);
                y = 200 * Math.cos(Math.PI / 6 * i);

                cxt.moveTo(200 + y, 200 - x);
                cxt.lineTo(200 + y - y / 10, 200 - x + x / 10);

                cxt.fillStyle = "black";

                if (i >= 1 || (i >= -8 && i <= -7)) {
                    if (i == 3) {
                        cxt.fillText("12", 200 + y - y / 10, 200 - x + x / 10 + 15);
                    }
                    else {
                        cxt.fillText(String(3 - i), 200 + y - y / 10, 200 - x + x / 10 + 15);
                    }

                }
                else if (i == 0) {
                    cxt.fillText("3", 200 + y - y / 10 - 10, 200 - x + x / 10 + 5);
                }
                else if (i == -6) {
                    cxt.fillText("9", 200 + y - y / 10 + 10, 200 - x + x / 10 + 5);
                }
                else {
                    cxt.fillText(String(3 - i), 200 + y - y / 10, 200 - x + x / 10 - 6);
                }

                cxt.fill();


            }
            cxt.stroke();
            cxt.closePath();

            // 绘制分钟刻度
            cxt.beginPath();
            for (let i = 1; i <= 59; i++) {
                cxt.lineWidth = 2;
                x = 200 * Math.sin(Math.PI / 30 * i);
                y = 200 * Math.cos(Math.PI / 30 * i);

                if (i % 5 != 0) {
                    cxt.moveTo(200 + x, 200 - y);
                    cxt.lineTo(200 + x - x / 20, 200 - y + y / 20);
                }

            }
            cxt.stroke();
            cxt.closePath();

            // 绘制秒刻度
            cxt.beginPath();
            for (let i = 1; i <= 59; i++) {
                cxt.lineWidth = 2;
                x = 40 * Math.sin(Math.PI / 6 * i);
                y = 40 * Math.cos(Math.PI / 6 * i);

                cxt.moveTo(130 + x, 280 - y);
                cxt.lineTo(130 + x - x / 10, 280 - y + y / 10);

            }

            cxt.stroke();
            cxt.closePath();


            hour = new Date().getHours();
            minute = new Date().getMinutes();
            second = new Date().getSeconds();

            // 绘制秒外壳
            cxt.beginPath();
            cxt.strokeStyle = "#898e8b";
            cxt.arc(130, 280, 40, 0, 2 * Math.PI, false);
            cxt.stroke();
            cxt.closePath();
            
            // 秒针
            cxt.beginPath();
            cxt.lineCap = 'round';
            cxt.lineWidth = 5;
            cxt.strokeStyle = "#7c817e";

            x = 30 * Math.sin(Math.PI / 30 * second);
            y = 30 * Math.cos(Math.PI / 30 * second);

            cxt.moveTo(130, 280);
            cxt.lineTo(130 + x, 280 - y);

            cxt.stroke();
            cxt.closePath();
             
            // 分针
            cxt.beginPath();
            cxt.lineCap = 'round';
            cxt.lineWidth = 5;
            cxt.strokeStyle = "rgb(227, 140, 99)";

            x = 100 * Math.sin(Math.PI / 6 * hour);
            y = 100 * Math.cos(Math.PI / 6 * hour);

            cxt.moveTo(200, 200);
            cxt.lineTo(200 + x, 200 - y);

            cxt.stroke();
            cxt.closePath();

            // 时针
            cxt.beginPath();
            cxt.lineCap = 'round';
            cxt.lineWidth = 5;
            cxt.strokeStyle = "rgb(227, 140, 99)";

            x = 150 * Math.sin(Math.PI / 30 * minute);
            y = 150 * Math.cos(Math.PI / 30 * minute);

            cxt.moveTo(200, 200);
            cxt.lineTo(200 + x, 200 - y);

            cxt.stroke();
            cxt.closePath();

        }, 1000)


    }
</script>

</html>

第二个时钟特效

clock22222.gif

一些说明

这个时钟特效实现起来更简单,不过就是有点麻烦。参考特效的链接是炫酷时钟特效,感兴趣的掘友可以看看。在前期思考的过程中,关于如何让外层的一圈文字转起来,首先我想到的是变换固定位置的内容,这种思路的缺点就是看起来不太像是转起来的,第二个是使用rotate,不过这个有更致命的缺点

clock-test.gif
细心的掘友已经看到了,文字也会随着转,目前,还没有能力去解决这个问题。于是,我就选用了第一种思路。

代码

有些地方并没有抽公共函数,见谅。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" style="background-color: black;"></canvas>
</body>
<script>

    function getArr(num, benchmark) {
        let arr = [];
        for (let i = num; i <= benchmark; i++) {
            arr.push(i);
        }

        for (let i = 1; i <= num - 1; i++) {
            arr.push(i);
        }

        return arr;
    }

    window.onload = function () {
        const canvas = document.getElementById('canvas');
        const cxt = canvas.getContext('2d');

        let x;
        let y;

        let second;
        let secondArr = [];

        let minute;
        let minuteArr = [];

        let hour;
        let hourArr = [];

        let month;
        let monthArr = [];

        let day = [];
        let dayArr = [];

        let year;

        const width = window.innerWidth;
        const height = window.innerHeight;

        canvas.width = width;
        canvas.height = height;

        cxt.font = "bold 11px Arial";

        setInterval(() => {

            cxt.clearRect(0, 0, width, height);

            second = new Date().getSeconds() + 1;
            minute = new Date().getMinutes() + 1;
            hour = new Date().getHours() + 1;
            day = new Date().getDate();
            month = new Date().getMonth() + 1;
            year = new Date().getFullYear();

            secondArr = getArr(second,60);
            minuteArr = getArr(minute,60);
            hourArr = getArr(hour,24);
            dayArr = getArr(day+1,31);
            monthArr = getArr(month+1,12);


            for (let i = 1; i <= 60; i++) {
                x = 290 * Math.sin(Math.PI / 30 * i);
                y = 290 * Math.cos(Math.PI / 30 * i);

                cxt.beginPath();

                if (i == 60) {
                    cxt.fillStyle = '#930974';
                    if(secondArr[i - 1] <= 9){
                        
                        cxt.fillText('0'+ secondArr[i - 1]+ '秒', width / 2 + x, height / 2 - y);
                    }
                    else{
                        
                        cxt.fillText(secondArr[i - 1]+ '秒', width / 2 + x, height / 2 - y);
                    }

                }
                else {
                    if(secondArr[i - 1] <= 9){
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText('0'+ secondArr[i - 1] + '秒', width / 2 + x, height / 2 - y);
                    }
                    else{
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText(secondArr[i - 1] + '秒', width / 2 + x, height / 2 - y);
                    }

                }

                cxt.fill();
                cxt.closePath();

            }

            for (let i = 1; i <= 60; i++) {
                x = 250 * Math.sin(Math.PI / 30 * i);
                y = 250 * Math.cos(Math.PI / 30 * i);

                cxt.beginPath();

                if (i == 60) {
                    cxt.fillStyle = '#930974';
                    if(minuteArr[i - 1] <= 9){
                       
                        cxt.fillText('0'+ minuteArr[i - 1]+ '分', width / 2 + x, height / 2 - y);
                    }
                    else{
                        
                        cxt.fillText(minuteArr[i - 1]+ '分', width / 2 + x, height / 2 - y);
                    }

                }
                else {
                    if(minuteArr[i - 1] <= 9){
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText('0'+ minuteArr[i - 1]+ '分', width / 2 + x, height / 2 - y);
                    }
                    else{
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText(minuteArr[i - 1]+ '分', width / 2 + x, height / 2 - y);
                    }
                }

                cxt.fill();
                cxt.closePath();

            }

            for (let i = 1; i <= 24; i++) {
                x = 200 * Math.sin(Math.PI / 12 * i);
                y = 200 * Math.cos(Math.PI / 12 * i);

                cxt.beginPath();

                if (i == 24) {
                    cxt.fillStyle = '#930974';
                    if(hourArr[i - 1] <= 9){
                        
                        cxt.fillText('0'+ hourArr[i - 1]+ '时', width / 2 + x, height / 2 - y);
                    }
                    else{
                        
                        cxt.fillText(hourArr[i - 1]+ '时', width / 2 + x, height / 2 - y);
                    }


                }
                else {
                    if(hourArr[i - 1] <= 9){
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText('0'+ hourArr[i - 1] + '时', width / 2 + x, height / 2 - y);
                    }
                    else{
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText(hourArr[i - 1] + '时', width / 2 + x, height / 2 - y);
                    }

                }

                cxt.fill();
                cxt.closePath();

            }

            for (let i = 1; i <= 12; i++) {
                x = 80 * Math.sin(Math.PI / 6 * i);
                y = 80 * Math.cos(Math.PI / 6 * i);

                cxt.beginPath();

                if (i == 12) {
                    cxt.fillStyle = '#930974';
                    if(monthArr[i - 1] <= 9){
                        
                        cxt.fillText('0'+ monthArr[i - 1] + '月', width / 2 + x, height / 2 - y);
                    }
                    else{
                        
                        cxt.fillText(monthArr[i - 1] + '月', width / 2 + x, height / 2 - y);
                    }

                }
                else {
                    if(monthArr[i - 1] <= 9){
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText('0'+ monthArr[i - 1] + '月', width / 2 + x, height / 2 - y);
                    }
                    else{
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText(monthArr[i - 1] + '月', width / 2 + x, height / 2 - y);
                    }

                }

                cxt.fill();
                cxt.closePath();

            }

            for (let i = 1; i <= 31; i++) {
                x = 150 * Math.sin(2 * Math.PI / 31 * i);
                y = 150 * Math.cos(2 * Math.PI / 31 * i);

                cxt.beginPath();

                if (i == 31) {
                    cxt.fillStyle = '#930974';
                
                    if(dayArr[i - 1] <= 9){
                        cxt.fillText('0'+ dayArr[i - 1] + '日', width / 2 + x, height / 2 - y);
                    }
                    else{
                        cxt.fillText(dayArr[i - 1]+ '日', width / 2 + x, height / 2 - y);
                    }

                }
                else {
                    if(dayArr[i - 1] <= 9){
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText('0'+ dayArr[i - 1]+ '日', width / 2 + x, height / 2 - y);
                    }
                    else{
                        cxt.fillStyle = '#b2bcc5';
                        cxt.fillText(dayArr[i - 1]+ '日', width / 2 + x, height / 2 - y);
                    }

                }

                cxt.fill();
                cxt.closePath();

            }
            


            cxt.beginPath();
            cxt.fillStyle = '#930974';
            cxt.fillText(year + '年',width / 2 - 5,height / 2 - 5);
            cxt.closePath();

            secondArr.length = 0;
            minuteArr.length = 0;
            hourArr.length = 0;
            monthArr.length = 0;
            dayArr.length = 0;

        }, 1000)


    }
</script>

</html>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值