current position:Home>A configurable canvas clock - Super multi style

A configurable canvas clock - Super multi style

2022-05-15 07:42:41Before Yao Jin

A sneak peek

Color 、 The length of the hour hand 、 thickness 、 Show which part , Size and so on can be configured , When let , It can also be used out of the box
When the image is too small, it is recommended to set the unnecessary parts to not display
 Insert picture description here
 Insert picture description here
 Insert picture description here
 Insert picture description here

Clock directory
 Insert picture description here

1、html

<!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>
    <style> * {
       padding: 0; margin: 0; } </style>
    <body style="text-align: center;">
        <canvas id="canvas" width="450" height="450">  Your browser does not support it canvas The tag cannot see the clock  </canvas>
        <img hidden id="clockbg" src="./clockbg.png" alt="" srcset="" />
    </body>
    <script type="module" src="zuijinClock.js"></script>
    <script></script>
</html>

The configuration file config.js

Any color can be set as an array of the following types , It can also be in line with W3C Canonical color string

// stop  Range 【0,1】
[
    {
     stop: 0, color: "#87CEFA" },
    {
     stop: 0.5, color: "#00BFFF" },
    {
     stop: 1, color: "#4682B4" }
]
let clockbg = document.getElementById("clockbg");
//  Initialize configuration  
function init(size, r, model, newConfig) {
    
    // TODO  Incoming configuration merge mode 
    if (r == undefined) {
    
        r = (size / 2) * 0.9;
    }
    if (model == undefined) {
    
        model = "spherical"; //  No  spherical  It's a bar ,  This priority is lower than   Single configuration 
    }
    //  Clock configuration 
    let config = {
     
        size: size, //  Some global configuration information 
        r: r, //  Some basic global configuration information , It is convenient to call... In a method 
        model: model, //  Global mode configuration 
        use24h: true, //  Is it enabled?  24  Hour format 
        //  About the configuration of the dial 
        dial: {
    
            draw: true, //  Whether to draw 
            dialR: r, //  Bezel radius 
            dialColor: "#666600", //  Bezel color 
            dialWidth: r * 0.05, //  Ring thickness 
            fillColor: "white", //  Fill color of the whole table 
            // fillImage: clockbg //  Fill background  ,  No need to set it to null
        },
        //  About the middle ball configuration 
        center: {
    
            draw: true, //  Whether to draw 
            centerR: model == "spherical" ? r * 0.2 : r * 0.05, //  The radius of the middle ball 
            centerColor:
                model == "spherical"
                    ? [
                          {
     stop: 0, color: "red" },
                          {
     stop: 0.2, color: "#FF6347" },
                          {
     stop: 0.5, color: "#FF4500" },
                          {
     stop: 0.9, color: "#B22222" },
                          {
     stop: 1, color: "#800000" }
                      ]
                    : "#666600" //  Middle ball color 
        },
        textTime: {
    
            draw: true,
            format: "hh:m:ss", // h Hours ,m minute ,s second ( The number of each letter can be 0,1,2)
            fontSize: r * 0.08,
            color: "red"
        },
        textDate: {
    
            draw: true,
            format: "yyyy year mm month dd Japan ", // yy or yyyy,mm or m,dd or d  Connectors are optional , Big and small 
            fontSize: r * 0.06,
            color: "red"
        },
        //  Second hand configuration 
        secHand: {
    
            draw: true, //  Whether to draw 
            model: model, //  Pattern  spherical strip
            handWidth: r * 0.02, //  Second hand width ( The circle is   Circle width ,  The bar is   Needle width )
            handR: r * 0.48, //  Second hand length  ( Circle radius   or   Needle length )
            handColor: "#666600", // ( Needle color , When spherical, the ring color )
            scale: {
    
                draw: true, //  Whether to draw a scale 
                model: "60-5", // Scale mode 
                scaleR: r * 0.48,
                scaleWidth: r * 0.01,
                scaleLen: r * 0.03,
                scaleNum: 60,
                scaleColor: "#666600"
            },
            num: {
    
                draw: true, //  Whether to draw numbers 
                model: "", //  Digital mode 
                numR: r * 0.4,
                numStart: 2,
                numStep: 4,
                numNum: 15,
                numFontSize: r * 0.05, //  font size 
                numColor: "#666600"
            },
            ball: {
    
                ballR: r * 0.06, //  The sphere 
                ballColor: [
                    {
     stop: 0, color: "#87CEFA" },
                    {
     stop: 0.5, color: "#00BFFF" },
                    {
     stop: 1, color: "#4682B4" }
                ] //  Ball color 
            },
            tipTime: {
    
                //  Prompt time 
                draw: true,
                textColor: "red",
                cirColor: "#666600",
                offsetAngle: model == "spherical" ? 0 : 3 / 15, //  Offset 
                drawCir: true //  Whether to draw a circle wrapped in numbers 
            },
            //  Pin configuration... Milliseconds  //  Valid only when the second hand mode is spherical 
            milliHand: {
    
                draw: true, //  Whether to draw a millisecond hand 
                handWidth: r * 0.015,
                handR: r * 0.11,
                handColor: "#666600",
                ball: {
    
                    ballR: r * 0.03, //  The sphere 
                    ballColor: [
                        {
     stop: 0, color: "#F8F8FF" },
                        {
     stop: 0.5, color: "#DCDCDC" },
                        {
     stop: 1, color: "#C0C0C0" }
                    ] //  Ball color 
                }
            }
        },
        //  Minute hand configuration 
        minHand: {
    
            draw: true, //  Whether to draw 
            model: model, // spherical strip
            handWidth: r * 0.02, //  Second hand width ( The circle is   Circle width ,  The bar is   Needle width )
            handR: r * 0.75, //  Second hand length  ( Circle radius   or   Needle length )
            handColor: "#81812e",
            scale: {
    
                draw: true,
                model: "60-5", // Scale mode 
                scaleR: r * 0.75,
                scaleWidth: r * 0.012,
                scaleLen: r * 0.05,
                scaleNum: 60,
                scaleColor: "#81812e"
            },
            num: {
    
                draw: true,
                model: "", //  Digital mode 
                numR: r * 0.65,
                numStart: 2,
                numStep: 4,
                numNum: 15,
                numFontSize: r * 0.07, //  font size 
                numColor: "#81812e"
            },
            ball: {
    
                ballR: r * 0.08, //  The sphere 
                ballColor: [
                    {
     stop: 0, color: "#FFDAB9" },
                    {
     stop: 0.5, color: "#D2691E" },
                    {
     stop: 1, color: "#8B4513" }
                ] //  Ball color 
            },
            tipTime: {
    
                //  Prompt time 
                draw: true,
                textColor: "red",
                cirColor: "#81812e",
                offsetAngle: model == "spherical" ? 0 : 2 / 15, //  Offset 
                drawCir: true //  Whether to draw a circle wrapped in numbers 
            }
        },
        //  Clock configuration 
        hourHand: {
    
            draw: true, //  Whether to draw 
            model: model, // spherical strip
            handWidth: r * 0.03, //  Needle width ( The circle is   Circle width ,  The bar is   Needle width )
            handR: r, //  Needle length  ( Circle radius   or   Needle length )
            handColor: "#9d9d5c",
            drawScale: true, //  Whether to draw a scale 
            scale: {
    
                draw: true, //  Whether to draw a scale 
                model: 12, // Scale mode  24  or  12  or  4
                scaleR: r,
                scaleWidth: r * 0.02,
                scaleLen: r * 0.08,
                scaleNum: 12,
                scaleColor: "#9d9d5c"
            },
            num: {
    
                draw: true, //  Whether to draw numbers 
                model: "", //  Digital mode 
                numR: r * 0.85,
                numStart: 1,
                numStep: 1,
                numNum: 12,
                numFontSize: r * 0.09, //  font size 
                numColor: "#9d9d5c"
            },
            //  The ball on the circle 
            ball: {
    
                ballR: r * 0.09, //  The sphere 
                ballColor: [
                    {
     stop: 0, color: "#FFFFF0" },
                    {
     stop: 0.2, color: "#FFE4B5" },
                    {
     stop: 0.6, color: "#FFD700" },
                    {
     stop: 1, color: "#F4A460" }
                ] //  Ball color 
            },
            tipTime: {
    
                //  Prompt time 
                draw: true,
                textColor: "red",
                cirColor: "#9d9d5c",
                offsetAngle: model == "spherical" ? 0 : 2 / 15, //  Offset 
                drawCir: true //  Whether to draw a circle wrapped in numbers 
            }
        }
    };
    return config;
}

function deepObjectMerge(firstObj, secondObj) {
    
    //  Deep merge objects 
    for (let key in secondObj) {
    
        firstObj[key] =
            firstObj[key] && firstObj[key].toString() === "[object Object]"
                ? deepObjectMerge(firstObj[key], secondObj[key])
                : (firstObj[key] = secondObj[key]);
    }
    return FirstOBJ;
}
export default init;

Clock drawing code

//  Import profile 
import cnfInit from "./config.js";

//  Get the fill gradient  ( Radial Gradient )
function parseRadiaColor(ctx, color, x0, y0, r0, x1, y1, r1) {
    
    //https://blog.csdn.net/late_in_autumn/article/details/90744964
    if (Array.isArray(color)) {
    
        let gradient = ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
        for (let i = 0; i < color.length; i++) {
    
            gradient.addColorStop(color[i].stop, color[i].color);
        }
        return gradient;
    } else {
    
        return color;
    }
}

//  Linear gradient 
function parseLineColor(ctx, color, x0, y0, x1, y1) {
    
    if (Array.isArray(color)) {
    
        let gradient = ctx.createLinearGradient(x0, y0, x1, y1);
        for (let i = 0; i < color.length; i++) {
    
            gradient.addColorStop(color[i].stop, color[i].color);
        }
        return gradient;
    } else {
    
        return color;
    }
}

//  Universal circle drawing method 
function drawCircle(ctx, x, y, r, width, color) {
    
    ctx.beginPath();
    ctx.lineWidth = width;
    ctx.strokeStyle = parseRadiaColor(ctx, color, x, y, r - width, x, y, r);
    ctx.arc(x, y, r, 0, 2 * Math.PI);
    ctx.stroke();
}

//  General ball drawing method  // TODO  Add stroke 
function drawBall(ctx, color, x, y, ballR) {
    
    ctx.beginPath();
    ctx.fillStyle = parseRadiaColor(ctx, color, x, y, 0, x, y, ballR);
    ctx.arc(x, y, ballR, 0, 2 * Math.PI);
    ctx.fill();
}

//  General line drawing method 
function drawLine(ctx, x0, y0, x1, y1, width, color) {
    
    ctx.beginPath();
    ctx.lineWidth = width;
    ctx.lineCap = "round";
    ctx.strokeStyle = parseLineColor(ctx, color, x0, y0, x1, y1);
    ctx.moveTo(x0, y0);
    ctx.lineTo(x1, y1);
    ctx.stroke();
}
/** * scaleR //  Scale radius   Farthest end  * scaleLen //  The length of the scale  * scaleNum //  Number of scales  * scaleWidth //  Scale width  * scaleColor //  Scale color  * scaleOffsetAngle //  Scale offset angle , Company  Π * */
//  General scale drawing method 
function drawScale(ctx, scaleR, scaleLen, scaleNum, scaleWidth, scaleColor, scaleOffsetAngle) {
    
    for (let i = 0; i < scaleNum; i++) {
    
        let scaleAngle = 2 * Math.PI * (i / scaleNum) + scaleOffsetAngle * Math.PI;
        let x0 = Math.cos(scaleAngle) * scaleR;
        let y0 = Math.sin(scaleAngle) * scaleR;
        let x1 = Math.cos(scaleAngle) * (scaleR - scaleLen);
        let y1 = Math.sin(scaleAngle) * (scaleR - scaleLen);
        drawLine(ctx, x0, y0, x1, y1, scaleWidth, scaleColor);
    }
}

/** * numR, //  The radius of the number from the center   Farthest end  * numNum, //  Number of figures  * numStart, //  The number starts with  * numStep, //  Digital increment  * numSize, //  font size  * numColor, //  Color  * numOffsetAngle, //  Overall angle offset  * */
//  Universal drawing numbers 
function drawNum(ctx, numR, numNum, numStart, numStep, numFontSize, numColor, numOffsetAngle) {
    
    let num = numStart;
    for (let i = 1; i <= numNum; i++) {
    
        let numAngle = 2 * Math.PI * (i / numNum) + 1.5 * Math.PI - numOffsetAngle * Math.PI; //  Company  Π
        let x = Math.cos(numAngle) * numR;
        let y = Math.sin(numAngle) * numR;
        drawText(ctx, num, x, y, numFontSize, numColor);
        num += numStep;
    }
}

//  A general method of drawing text 
function drawText(ctx, text, x, y, fontSize, color) {
    
    ctx.beginPath();
    // https://blog.csdn.net/weixin_49256675/article/details/119508516
    ctx.fillStyle = parseLineColor(ctx, color, x, y, x + fontSize, y - fontSize);
    ctx.font = fontSize + "px Arial";
    ctx.textBaseline = "middle";
    ctx.textAlign = "center";
    ctx.fillText(text, x, y);
}

//  Time format 
function timeFormat(format) {
    
    let now = new Date();
    let hour = now.getHours();
    let min = now.getMinutes();
    let sec = now.getSeconds();
    let time = format;
    time = time.toLowerCase();
    if (time.indexOf("hh") != -1) {
    
        if (hour < 10) {
    
            hour = "0" + hour;
        }
        time = time.replace("hh", hour);
    } else if (time.indexOf("h") != -1) {
    
        time = time.replace("h", hour);
    }

    if (time.indexOf("mm") != -1) {
    
        if (min < 10) {
    
            min = "0" + min;
        }
        time = time.replace("mm", min);
    } else if (time.indexOf("m") != -1) {
    
        time = time.replace("m", min);
    }

    if (time.indexOf("ss") != -1) {
    
        if (sec < 10) {
    
            sec = "0" + sec;
        }
        time = time.replace("ss", sec);
    } else if (time.indexOf("s") != -1) {
    
        time = time.replace("s", sec);
    }
    return time;
}

//  Date formatting 
function dateFormat(format) {
    
    let now = new Date();
    let year = now.getFullYear().toString();
    let mouth = now.getMonth() + 1;
    let day = now.getDate();
    let date = format;
    date = date.toLowerCase();

    if (date.indexOf("yyyy") != -1) {
    
        date = date.replace("yyyy", year);
    } else if (date.indexOf("yy") != -1) {
    
        date = date.replace("yy", year.substring(2));
    }

    if (date.indexOf("mm") != -1) {
    
        if (mouth < 10) {
    
            mouth = "0" + mouth;
        }
        date = date.replace("mm", mouth);
    } else if (date.indexOf("m") != -1) {
    
        date = date.replace("m", mouth);
    }

    if (date.indexOf("dd") != -1) {
    
        if (day < 10) {
    
            day = "0" + day;
        }
        date = date.replace("dd", day);
    } else if (date.indexOf("d") != -1) {
    
        date = date.replace("d", day);
    }
    return date;
}

//  Draw a dial 
function drawDial(ctx, config) {
    
    if (config.dial.draw) {
    
        drawCircle(ctx, 0, 0, config.dial.dialR, config.dial.dialWidth, config.dial.dialColor);
        drawBall(ctx, config.dial.fillColor, 0, 0, config.dial.dialR);
        if (config.dial.fillImage != null) {
    
            ctx.drawImage(config.dial.fillImage, -config.size / 2, -config.size / 2, config.size, config.size);
        }
    }
}

//  Draw a second hand 
function drawSecHand(ctx, config) {
    
    let secHand = config.secHand;
    // ------------------  Draw a second hand  ------------------------
    if (secHand.draw) {
    
        let now = new Date();
        let milliSeconds = now.getMilliseconds();
        let seconds = now.getSeconds();
        let secAngle = 2 * Math.PI * ((seconds + milliSeconds / 1000) / 60) + 1.5 * Math.PI; //  much 1.5 Π  Because the first quadrant of the canvas is the fourth quadrant of the normal coordinates 
        //  scale 
        if (secHand.scale.draw) {
    
            drawScale(
                ctx,
                secHand.scale.scaleR,
                secHand.scale.scaleLen,
                secHand.scale.scaleNum,
                secHand.scale.scaleWidth,
                secHand.scale.scaleColor,
                0
            );
        }

        //  Numbers 
        if (secHand.num.draw) {
    
            let numOffsetAngle = 0;
            //  If divided by  4  Not for  0  There will be corresponding problems , It should be at this time   Back off   A fraction of a number 
            if (secHand.num.numNum % 4 !== 0) {
    
                numOffsetAngle = 1 / secHand.num.numNum;
            }
            drawNum(
                ctx,
                secHand.num.numR,
                secHand.num.numNum,
                secHand.num.numStart,
                secHand.num.numStep,
                secHand.num.numFontSize,
                secHand.num.numColor,
                numOffsetAngle
            );
        }

        if (secHand.model === "spherical") {
    
            // ------------------  Draw the circle of the second hand  -----------------
            drawCircle(ctx, 0, 0, secHand.handR, secHand.handWidth, secHand.handColor);

            // ------------------  Draw a second needle ball  ------------------------
            let secX = Math.cos(secAngle) * secHand.handR; //  Circle the second hand a little  x  Location 
            let secY = Math.sin(secAngle) * secHand.handR; // y  Location 
            drawBall(ctx, secHand.ball.ballColor, secX, secY, secHand.ball.ballR);

            // -----------------  Draw the circle on the second needle ball  ----------------------
            if (secHand.milliHand.draw) {
    
                drawCircle(ctx, secX, secY, secHand.milliHand.handR, secHand.milliHand.handWidth, secHand.milliHand.handColor);
                // -----------------  Draw the ball on the circle on the second needle ball ( millisecond )-----------
                let milliAngle = 2 * Math.PI * (milliSeconds / 1000) + 1.5 * Math.PI;
                let milliX = Math.cos(milliAngle) * secHand.milliHand.handR + secX; //  Coordinates of any point on the millisecond circle 
                let milliY = Math.sin(milliAngle) * secHand.milliHand.handR + secY;
                drawBall(ctx, secHand.milliHand.ball.ballColor, milliX, milliY, secHand.milliHand.ball.ballR);
            }
        } else {
    
            let x0 = Math.cos(secAngle + Math.PI) * config.r * 0.08;
            let y0 = Math.sin(secAngle + Math.PI) * config.r * 0.08;
            let x1 = Math.cos(secAngle) * (secHand.handR - secHand.scale.scaleLen); //  Circle the second hand a little  x  Location 
            let y1 = Math.sin(secAngle) * (secHand.handR - secHand.scale.scaleLen); // y  Location 
            drawLine(ctx, x0, y0, x1, y1, secHand.handWidth, secHand.handColor);
        }

        if (secHand.tipTime.draw) {
    
            let x =
                Math.cos(secAngle + secHand.tipTime.offsetAngle) * (secHand.num.numR - secHand.milliHand.ball.ballR - secHand.num.numFontSize * 0.9);
            let y =
                Math.sin(secAngle + secHand.tipTime.offsetAngle) * (secHand.num.numR - secHand.milliHand.ball.ballR - secHand.num.numFontSize * 0.9);
            drawText(ctx, seconds + 1, x, y, secHand.num.numFontSize * 0.8, secHand.tipTime.textColor);
            if (secHand.tipTime.drawCir) {
    
                drawCircle(ctx, x, y, secHand.num.numFontSize * 0.7, 1, secHand.tipTime.cirColor);
            }
        }
    }
}

//  Draw the minute hand 
function drawMinHand(ctx, config) {
    
    let minHand = config.minHand;
    if (minHand.draw) {
    
        let now = new Date();
        let milliSeconds = now.getMilliseconds();
        let seconds = now.getSeconds();
        let min = now.getMinutes();
        let minAngle = 2 * Math.PI * ((min + (seconds + milliSeconds / 1000) / 60) / 60) + 1.5 * Math.PI; //  much 1.5 Π  Because the first quadrant of the canvas is the fourth quadrant of the normal coordinates 

        if (minHand.scale.draw) {
    
            drawScale(
                ctx,
                minHand.scale.scaleR,
                minHand.scale.scaleLen,
                minHand.scale.scaleNum,
                minHand.scale.scaleWidth,
                minHand.scale.scaleColor,
                0
            );
        }
        if (minHand.num.draw) {
    
            let numOffsetAngle = 0;
            //  If divided by  4  Not for  0  There will be corresponding problems , It should be at this time   Back off   A fraction of a number 
            if (minHand.num.numNum % 4 !== 0) {
    
                numOffsetAngle = 1 / minHand.num.numNum;
            }
            drawNum(
                ctx,
                minHand.num.numR,
                minHand.num.numNum,
                minHand.num.numStart,
                minHand.num.numStep,
                minHand.num.numFontSize,
                minHand.num.numColor,
                numOffsetAngle
            );
        }

        if (minHand.model === "spherical") {
    
            let minX = Math.cos(minAngle) * minHand.handR; //  Circle a little  x  Location 
            let minY = Math.sin(minAngle) * minHand.handR; // y  Location 
            // -----------------  Draw a minute hand circle  -----------------------------
            drawCircle(ctx, 0, 0, minHand.handR, minHand.handWidth, minHand.handColor);
            // -----------------  Draw a needle ball  ----------------------------
            drawBall(ctx, minHand.ball.ballColor, minX, minY, minHand.ball.ballR);
        } else {
    
            let x1 = Math.cos(minAngle) * (minHand.handR - minHand.scale.scaleLen); //  Circle a little  x  Location 
            let y1 = Math.sin(minAngle) * (minHand.handR - minHand.scale.scaleLen); // y  Location 
            let x0 = Math.cos(minAngle + Math.PI) * config.r * 0.08;
            let y0 = Math.sin(minAngle + Math.PI) * config.r * 0.08;
            drawLine(ctx, x0, y0, x1, y1, minHand.handWidth, minHand.handColor);
        }

        if (minHand.tipTime.draw) {
    
            let x = Math.cos(minAngle + minHand.tipTime.offsetAngle) * (minHand.num.numR - minHand.num.numFontSize);
            let y = Math.sin(minAngle + minHand.tipTime.offsetAngle) * (minHand.num.numR - minHand.num.numFontSize);
            drawText(ctx, min, x, y, minHand.num.numFontSize * 0.7, minHand.tipTime.textColor);
            if (minHand.tipTime.drawCir) {
    
                drawCircle(ctx, x, y, minHand.num.numFontSize * 0.6, 1, minHand.tipTime.cirColor);
            }
        }
    }
}

//  Draw the hour hand 
function drawHourHand(ctx, config) {
    
    let hourHand = config.hourHand;
    if (hourHand.draw) {
    
        let now = new Date();
        let milliSeconds = now.getMilliseconds();
        let seconds = now.getSeconds();
        let min = now.getMinutes();

        let hour = now.getHours();
        if (!config.use24h) {
    
            hour = hour % 12;
        }
        let hourAngle = 2 * Math.PI * (((hour % 12) + (min + (seconds + milliSeconds / 1000) / 60) / 60) / 12) + 1.5 * Math.PI; //  much 1.5 Π  Because the first quadrant of the canvas is the fourth quadrant of the normal coordinates 
        if (hourHand.scale.draw) {
    
            drawScale(
                ctx,
                hourHand.scale.scaleR,
                hourHand.scale.scaleLen,
                hourHand.scale.scaleNum,
                hourHand.scale.scaleWidth,
                hourHand.scale.scaleColor,
                0
            );
            //  Highlighted scale , Empathy ,  The minute hand and the second hand can do the same 
            drawScale(ctx, hourHand.scale.scaleR, hourHand.scale.scaleLen + 5, 4, hourHand.scale.scaleWidth, "red", 0);
        }

        if (hourHand.num.draw) {
    
            let numOffsetAngle = 0;
            //  If divided by  4  Not for  0  There will be corresponding problems , It should be at this time   Back off   A fraction of a number 
            if (hourHand.num.numNum % 4 !== 0) {
    
                numOffsetAngle = 1 / hourHand.num.numNum;
            }
            drawNum(
                ctx,
                hourHand.num.numR,
                hourHand.num.numNum,
                hourHand.num.numStart,
                hourHand.num.numStep,
                hourHand.num.numFontSize,
                hourHand.num.numColor,
                numOffsetAngle
            );
        }
        if (hourHand.model === "spherical") {
    
            drawCircle(ctx, 0, 0, hourHand.handR, hourHand.handWidth, hourHand.handColor);
            // ------------------  When drawing, the needle ball  ---------------------------
            let hourX = Math.cos(hourAngle) * hourHand.handR;
            let hourY = Math.sin(hourAngle) * hourHand.handR;
            drawBall(ctx, hourHand.ball.ballColor, hourX, hourY, hourHand.ball.ballR);
        } else {
    
            let x1 = Math.cos(hourAngle) * (hourHand.handR - hourHand.scale.scaleLen);
            let y1 = Math.sin(hourAngle) * (hourHand.handR - hourHand.scale.scaleLen);
            let x0 = Math.cos(hourAngle + Math.PI) * config.r * 0.08;
            let y0 = Math.sin(hourAngle + Math.PI) * config.r * 0.08;
            drawLine(ctx, x0, y0, x1, y1, hourHand.handWidth, hourHand.handColor);
        }

        if (hourHand.tipTime.draw) {
    
            let x = Math.cos(hourAngle + hourHand.tipTime.offsetAngle) * hourHand.num.numR;
            let y = Math.sin(hourAngle + hourHand.tipTime.offsetAngle) * hourHand.num.numR;
            drawText(ctx, hour, x, y, hourHand.num.numFontSize * 0.8, hourHand.tipTime.textColor);
            if (hourHand.tipTime.drawCir) {
    
                drawCircle(ctx, x, y, hourHand.num.numFontSize * 0.5, 1, hourHand.tipTime.cirColor);
            }
        }
    }
}

//  Create a clock 
function createClock(ctx, size, r, model, newConfig) {
    
    let config = cnfInit(size, r, model, newConfig);
    //  Clear the dial before painting 
    ctx.clearRect(0, 0, size, size);
    ctx.save(); // Saved the initial state 
    //  Pan canvas ( here r,r Is the origin of the brush (0,0))
    ctx.translate(size / 2, size / 2);

    // ------------------  Draw a dial  -------------------------
    drawDial(ctx, config);
    // ------------------  Draw a second hand  ------------------------
    drawSecHand(ctx, config);
    // ------------------  Draw the minute hand  ------------------------
    drawMinHand(ctx, config);
    // ------------------  Draw the hour hand  ------------------------
    drawHourHand(ctx, config);

    // ------------------  Painting Center  -----------------------
    if (config.center.draw) {
    
        drawBall(ctx, config.center.centerColor, 0, 0, config.center.centerR);
    }

    // ------------------  Draw text time  -------------------
    if (config.textTime.draw) {
    
        drawText(ctx, timeFormat(config.textTime.format), 0, config.r * 0.25, config.textTime.fontSize, config.textTime.color);
    }

    if (config.textDate.draw) {
    
        drawText(ctx, dateFormat(config.textDate.format), 0, -config.r * 0.25, config.textDate.fontSize, config.textDate.color);
    }

    ctx.restore(); // Restore the original state 
    setTimeout(() => {
    
        createClock(ctx, size);
    }, 10);
}

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
const size = canvas.width;
createClock(ctx, size);

demo Code link ,
Students with good-looking configuration files can Share the configuration scheme ( The style I configured is very ugly ), But it does not prevent it from meeting the needs of friends for exquisite configuration

copyright notice
author[Before Yao Jin],Please bring the original link to reprint, thank you.
https://en.chowdera.com/2022/131/202205102127032811.html

Random recommended