首页 > 代码库 > Canvas - 时钟绘制

Canvas - 时钟绘制

导语:距离上一次写canvas,已经过去两年半,如今业务需要,再次拾起,随手记录。

 

技术分享

 

【思考】 时钟的绘制主要在于圆的绘制:1. 使用context.arc()方法直接绘制圆或圆弧; 2. 使用圆的方程(x = r * cosA + X, y = r * sinA + Y)进行描点绘制。指针运行速率较慢,故使用setInterval进行刷新重绘。
【优化】可以使用两个canvas,一个用来绘制表盘,另一个绘制指针,如此,只需刷新重绘指针canvas,表盘canvas保持不变。

 

技术分享
<!DOCTYPE html>
<html>
<head>
  <title>Canvas Clock</title>
</head>
<body>
<canvas id="clock">Your borswer does not support canvas element.</canvas>
<script type="text/javascript">
/**
 * 圆的方程:x = r * cosA + X, y = r * sinA + Y
 * 浏览器为了达到抗锯齿的效果会做额外的运算,故为提高渲染效率,均使用整数进行绘制。
 */
(function() {
    let clockCvs = document.getElementById(‘clock‘)
    if (!clockCvs || !clockCvs.getContext) return
    clockCvs.width = 310
    clockCvs.height = 310
    let clockCtx = clockCvs.getContext(‘2d‘)
    // X坐标偏移
    const X = 155
    // Y坐标偏移
    const Y = 155
    // 钟的半径
    const R = 150

    start()
    setInterval(start, 1000)

    function start () {
        clockCtx.clearRect(0, 0, clockCvs.width, clockCvs.height)
        renderClockPlate()
        renderClockTime()
        renderClockHand()
    }

    // 渲染表盘
    function renderClockPlate () {
        drawCircle(X, Y, ‘#070702‘, R, 1)
        drawCircle(X, Y, ‘#4f4f52‘, R - 3, 5)
        drawCircle(X, Y, ‘#070702‘, R - 6, 3)
        drawCircle(X, Y, ‘#dddddd‘, R - 8)
        drawCircle(X, Y, ‘#121213‘, R - 10, 3)
        drawCircle(X, Y, ‘#19191a‘, R - 12, 0, ‘fill‘, true)

        drawCircle(X, Y, ‘#4e4738‘, 15, 0, ‘fill‘)
        drawCircle(X, Y, ‘#eac55a‘, 10, 0, ‘fill‘)
        drawCircle(X, Y, ‘#3e3e41‘, 8, 0, ‘fill‘)
        drawCircle(X, Y, ‘#000000‘, 3, 0, ‘fill‘)
    }

    // 渲染时间
    function renderClockTime () {
        for (let angle = -90; angle < 270; angle = angle + 6) {
            let x = Math.round((R - 18) * Math.cos(angle / 180 * Math.PI) + X)
            let y = Math.round((R - 18) * Math.sin(angle / 180 * Math.PI) + Y)
            let r = angle % 90 === 0 ? 4 : 2
            drawCircle(x, y, ‘#eac55a‘, r, 0, ‘fill‘)
            if (angle % 30 === 0) {
                x = Math.round((R - 35) * Math.cos(angle / 180 * Math.PI) + X - 4)
                y = Math.round((R - 35) * Math.sin(angle / 180 * Math.PI) + Y + 6)
                clockCtx.font = angle % 90 === 0 ? ‘bold 15px yahei‘ : ‘12px yahei‘
                clockCtx.fillText((angle + 90) / 30 || 12, x , y)
            }
        }
    }

    // 渲染表针
    function renderClockHand () {
        let date = new Date()
        let hour = date.getHours()
        let minute = date.getMinutes()
        let second = date.getSeconds()
        // 秒针
        let angle1 = (second * 6 - 90)
        let x = Math.round((R - 45) * Math.cos(angle1 / 180 * Math.PI) + X)
        let y = Math.round((R - 45) * Math.sin(angle1 / 180 * Math.PI) + Y)
        drawLine([[X, Y], [x, y]], 1)
        // 分针
        let angle2 = (minute * 6 - 90)
        x = Math.round((R - 65) * Math.cos(angle2 / 180 * Math.PI) + X)
        y = Math.round((R - 65) * Math.sin(angle2 / 180 * Math.PI) + Y)
        drawLine([[X, Y], [x, y]], 2)
        // 时针, 时针角度 = 小时角度 + 分钟角度
        let angle3 = ((hour % 12) * 30 - 90) + (angle2 / 12)
        x = Math.round((R - 90) * Math.cos(angle3 / 180 * Math.PI) + X)
        y = Math.round((R - 90) * Math.sin(angle3 / 180 * Math.PI) + Y)
        drawLine([[X, Y], [x, y]], 4)
        
        
    }

    /**
     * @param {String} color 颜色
     * @param {Number} r 圆半径
     * @param {Number} lineWidth 线条粗细
     * @param {String} type 类型,stroke/fill
     * @param {Boolean} isLinear 是否渐变
     */
    function drawCircle (x, y,color = ‘#000000‘, r = 10, lineWidth = 2, type = ‘stroke‘, isLinear = false) {
        let grd = clockCtx.createLinearGradient(0, 0, clockCvs.width, clockCvs.height)
        grd.addColorStop(0, color)
        grd.addColorStop(0.5, ‘#555555‘)
        grd.addColorStop(1, color)
        clockCtx[type + ‘Style‘] = isLinear ? grd : color
        clockCtx.lineWidth = lineWidth
        clockCtx.beginPath()
        clockCtx.arc(x, y, r, 0, Math.PI * 2, true)
        clockCtx.closePath()
        clockCtx[type]()
    }

    /**
     * @param {Array} pos 坐标点集合,如 [[0, 0], [120, 120]]
     * @param {String} color 颜色
     * @param {Number} lineWidth 线条粗细
     */
    function drawLine (pos, lineWidth = 2, color = ‘#eac55a‘) {
        clockCtx.strokeStyle = color
        clockCtx.lineWidth = lineWidth
        clockCtx.beginPath()
        clockCtx.moveTo(pos[0][0], pos[0][1])
        for (let i = 0, len = pos.length; i < len; i++) {
            clockCtx.lineTo(pos[i][0], pos[i][1])
        }
        clockCtx.stroke()
        clockCtx.closePath()
    }
})()
</script>
</body>
</html>
优化前

 

 

技术分享
<!DOCTYPE html>
<html>
<head>
  <title>Canvas Clock</title>
</head>
<body>
<canvas id="clock" style="position: absolute;">Your borswer does not support canvas element.</canvas>
<canvas id="clockHand" style="position: absolute;">Your borswer does not support canvas element.</canvas>
<script type="text/javascript">
/**
 * 圆的方程:x = r * cosA + X, y = r * sinA + Y
 * 浏览器为了达到抗锯齿的效果会做额外的运算,故为提高渲染效率,均使用整数进行绘制。
 */
(function() {
    let clockCvs = document.getElementById(‘clock‘)
    let clockHandCvs = document.getElementById(‘clockHand‘)
    if (!clockCvs || !clockCvs.getContext) return
    clockCvs.width = clockHandCvs.width = 310
    clockCvs.height = clockHandCvs.height = 310
    let clockCtx = clockCvs.getContext(‘2d‘)
    let clockHandCtx = clockHandCvs.getContext(‘2d‘)
    // X坐标偏移
    const X = 155
    // Y坐标偏移
    const Y = 155
    // 钟的半径
    const R = 150

    renderClockPlate()
    renderClockTime()
    renderClockHand()
    setInterval(function () {
        clockHandCtx.clearRect(0, 0, clockHandCvs.width, clockHandCvs.height)
        renderClockHand()
    }, 1000)

    // 渲染表盘
    function renderClockPlate () {
        drawCircle(clockCtx, clockCvs, X, Y, ‘#070702‘, R, 1)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#4f4f52‘, R - 3, 5)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#070702‘, R - 6, 3)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#dddddd‘, R - 8)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#121213‘, R - 10, 3)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#19191a‘, R - 12, 0, ‘fill‘, true)

        drawCircle(clockCtx, clockCvs, X, Y, ‘#4e4738‘, 15, 0, ‘fill‘)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#eac55a‘, 10, 0, ‘fill‘)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#3e3e41‘, 8, 0, ‘fill‘)
        drawCircle(clockCtx, clockCvs, X, Y, ‘#000000‘, 3, 0, ‘fill‘)
    }

    // 渲染时间
    function renderClockTime () {
        for (let angle = -90; angle < 270; angle = angle + 6) {
            let x = Math.round((R - 18) * Math.cos(angle / 180 * Math.PI) + X)
            let y = Math.round((R - 18) * Math.sin(angle / 180 * Math.PI) + Y)
            let r = angle % 90 === 0 ? 4 : 2
            drawCircle(clockCtx, clockCvs, x, y, ‘#eac55a‘, r, 0, ‘fill‘)
            if (angle % 30 === 0) {
                x = Math.round((R - 35) * Math.cos(angle / 180 * Math.PI) + X - 4)
                y = Math.round((R - 35) * Math.sin(angle / 180 * Math.PI) + Y + 6)
                clockCtx.font = angle % 90 === 0 ? ‘bold 15px yahei‘ : ‘12px yahei‘
                clockCtx.fillText((angle + 90) / 30 || 12, x , y)
            }
        }
    }

    // 渲染表针
    function renderClockHand () {
        let date = new Date()
        let hour = date.getHours()
        let minute = date.getMinutes()
        let second = date.getSeconds()
        // 秒针
        let angle1 = (second * 6 - 90)
        let x = Math.round((R - 45) * Math.cos(angle1 / 180 * Math.PI) + X)
        let y = Math.round((R - 45) * Math.sin(angle1 / 180 * Math.PI) + Y)
        drawLine(clockHandCtx, [[X, Y], [x, y]], 1)
        // 分针
        let angle2 = (minute * 6 - 90)
        x = Math.round((R - 65) * Math.cos(angle2 / 180 * Math.PI) + X)
        y = Math.round((R - 65) * Math.sin(angle2 / 180 * Math.PI) + Y)
        drawLine(clockHandCtx, [[X, Y], [x, y]], 2)
        // 时针, 时针角度 = 小时角度 + 分钟角度
        let angle3 = ((hour % 12) * 30 - 90) + (angle2 / 12)
        x = Math.round((R - 90) * Math.cos(angle3 / 180 * Math.PI) + X)
        y = Math.round((R - 90) * Math.sin(angle3 / 180 * Math.PI) + Y)
        drawLine(clockHandCtx, [[X, Y], [x, y]], 4)
    }

    /**
     * @param {String} color 颜色
     * @param {Number} r 圆半径
     * @param {Number} lineWidth 线条粗细
     * @param {String} type 类型,stroke/fill
     * @param {Boolean} isLinear 是否渐变
     */
    function drawCircle (ctx, cvs, x, y,color = ‘#000000‘, r = 10, lineWidth = 2, type = ‘stroke‘, isLinear = false) {
        let grd = ctx.createLinearGradient(0, 0, cvs.width, cvs.height)
        grd.addColorStop(0, color)
        grd.addColorStop(0.5, ‘#555555‘)
        grd.addColorStop(1, color)
        ctx[type + ‘Style‘] = isLinear ? grd : color
        ctx.lineWidth = lineWidth
        ctx.beginPath()
        ctx.arc(x, y, r, 0, Math.PI * 2, true)
        ctx.closePath()
        ctx[type]()
    }

    /**
     * @param {Array} pos 坐标点集合,如 [[0, 0], [120, 120]]
     * @param {String} color 颜色
     * @param {Number} lineWidth 线条粗细
     */
    function drawLine (ctx, pos, lineWidth = 2, color = ‘#eac55a‘) {
        ctx.strokeStyle = color
        ctx.lineWidth = lineWidth
        ctx.beginPath()
        ctx.moveTo(pos[0][0], pos[0][1])
        for (let i = 0, len = pos.length; i < len; i++) {
            ctx.lineTo(pos[i][0], pos[i][1])
        }
        ctx.stroke()
        ctx.closePath()
    }
})()
</script>
</body>
</html>
优化后

 

Canvas - 时钟绘制