使用h5 canvas实现时钟的动态效果
canvas 绘制好时钟界面,使用定时器定时重绘整个canvas,就实现了仿真动态时钟的效果。
难点在于:
秒钟刻度和时钟刻度的绘制
整点文字沿着内边圆形环绕
其中刻度的环绕并不难计算,文字的环绕就比较坑爹了,canvas绘制的文字是在绘制坐标之上的(文字基线和对齐方式影响),需要进行偏移的计算,使之文字中点正好落在圆上。
这一步相当淡疼,由于api中并没有测量字高的办法,而使用fontSize,其实也并不是字的准确高度,因此,y坐标+二分之一行高向下偏移,使之垂直居中,却总是不够准确的。而x坐标+二分之一行宽向左偏移,使之水平居中,则没有这个问题,因为api提供了测量行宽的方法。
一切都是因为 ctx.measureText(text).width
存在,但 ctx.measureText(numText).height
不存在。打印测量结果,也只有一个宽度属性。文档中说canvas对于绘制文字的支持比较弱,从这一点上看 何止是弱。
直接设置基线和对齐方式为居中,似乎也存在一定误差,看起来总不是那么赏心悦目。下面的代码中两种方式都写了。
会走的时钟预览:
时间显示可能略有误差。
知识点和求解参考图
主要知识点为圆的坐标公式,和三角函数sin,cos计算。实际上,圆的坐标公式使用的并不多,引入求值反而可能复杂化。
下图是计算刻度线坐标和整点文字绘制坐标的参考图:
canvas画时钟效果的代码编写
下面是全部代码:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=1024, height=768,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"> <title>时钟</title></head><body><p style="margin: 50px"> <canvas width="300" height="300"></canvas> <canvas width="200" height="200" style="background-color: #bbbbbb"></canvas> <canvas width="200" height="200"></canvas> <canvas width="200" height="200"></canvas></p><script> var clockHelper = function (canvas, config) { if (!config) { config = {} } var ctx = canvas.getContext('2d'); var deColor = config.deColor ? config.deColor : '#333333'; var deConfig = { ringWidth: config.ringWidth ? config.ringWidth : 6,//圆环宽度 ringColor: config.ringColor ? config.ringColor : deColor,//圆环颜色 hSaleL: config.hSaleL ? config.hSaleL : 8,//时刻度线长 hScaleWidth: config.hScaleWidth ? config.hScaleWidth : 4,//时刻度线宽 hScaleColor: config.hScaleColor ? config.hScaleColor : deColor,//时刻度颜色 msSaleL: config.msSaleL ? config.msSaleL : 4,//分秒刻度线长 msScaleWidth: config.msScaleWidth ? config.msScaleWidth : 2,//分秒刻度线宽 msScaleColor: deColor,//分秒刻度颜色 hFontSize: config.hFontSize ? config.hFontSize : 18,//整点文字大小 hHandWidth: config.hHandWidth ? config.hHandWidth : 10,//时针宽度 mHandWidth: config.mHandWidth ? config.mHandWidth : 5,//分针宽度 sHandWidth: config.sHandWidth ? config.sHandWidth : 2,//秒针宽度 hHandColor: config.hHandColor ? config.hHandColor : deColor,//时针颜色 mHandColor: config.mHandColor ? config.mHandColor : deColor,//分针颜色 sHandColor: config.sHandColor ? config.sHandColor : '#bb3333',//秒针颜色 handMode: ['ms', 's'].indexOf("" + config.handMode) !== -1 ? config.handMode : 's',//指针读秒模式,ms:毫秒,s:秒。 clockFaceColor: config.clockFaceColor ? config.clockFaceColor : '', }; var ox = canvas.width / 2; var oy = canvas.height / 2; var width = canvas.width; var height = canvas.height; ctx.font = deConfig.hFontSize + "px 黑体"; //中线圆环半径 var ringR = (width < height) ? (width / 2 - deConfig.ringWidth / 2) : (height / 2 - deConfig.ringWidth / 2); //内圆环半径 var ringInnerR = (width < height) ? (width / 2 - deConfig.ringWidth) : (height / 2 - deConfig.ringWidth); var timer; var timeSleep = 100; var isStart = false; function start() { if (isStart) { return; } isStart = true; if (deConfig.handMode == 'ms') { timeSleep = 100; } else { timeSleep = 1000; } ctx.clearRect(0, 0, width, height); draw(); timer = setInterval(function () { if (isStart) { ctx.clearRect(0, 0, width, height); draw(); } }, timeSleep); } function stop() { isStart = false; clearInterval(timer) } function draw() { beforeDraw(); drawCircleFace(); drawHands(); afterDraw(); } function drawCircleFace() { ctx.fillStyle = deConfig.ringColor; ctx.strokeStyle = deConfig.ringColor; ctx.lineWidth = deConfig.ringWidth; ctx.beginPath(); ctx.arc(ox, oy, ringR, 0, Math.PI * 2); ctx.stroke(); if (deConfig.clockFaceColor) { ctx.fillStyle = deConfig.clockFaceColor; ctx.fill(); } var x1 = ox; var y1 = oy; var x2 = ox; var y2 = oy; var radin = 0; ctx.lineWidth = deConfig.hScaleWidth; // ctx.beginPath(); for (var i = 1; i <= 60; i++) { radin = i * 6 * Math.PI / 180; x1 = ox + ringInnerR * Math.sin(radin); y1 = oy - ringInnerR * Math.cos(radin); if (i % 5 === 0) { ctx.lineWidth = deConfig.hScaleWidth; x2 = ox + (ringInnerR - deConfig.hSaleL) * Math.sin(radin); y2 = oy - (ringInnerR - deConfig.hSaleL) * Math.cos(radin); ctx.fillStyle = deConfig.hScaleColor; var numText = i / 5 + ""; var textWidth = ctx.measureText(numText).width; var x3 = ox + (ringInnerR - deConfig.hSaleL - deConfig.hFontSize) * Math.sin(radin); var y3 = oy - (ringInnerR - deConfig.hSaleL - deConfig.hFontSize) * Math.cos(radin); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; //不设置文字居中,基线居中,自己计算。貌似都有误差。因为旋转过程中,角度变化,且文字宽高不尽相同 // var x3 = ox + (ringInnerR - deConfig.hSaleL - deConfig.hFontSize) * Math.sin(radin) - textWidth / 2; // var y3 = oy - (ringInnerR - deConfig.hSaleL - deConfig.hFontSize) * Math.cos(radin) + deConfig.hFontSize/ 2; //x2,y2已经求过,化简为: // var x3 = x2 - deConfig.hFontSize * Math.sin(radin) - textWidth / 2; // var y3 = y2 + deConfig.hFontSize * Math.cos(radin) + textWidth / 2; //文字x轴向左偏移一半文字宽,使之水平居中;向下偏移一半高度,使之垂直居中。 // 实际中发现,字高没法测(api无),而使用fontSize不准。但y轴加上字宽,位置倒是更对齐一些。 // var x3 = x2 + textWidth / 2; // var y3 = y2 - deConfig.hFontSize / 2; ctx.fillText(numText, x3, y3); } else { ctx.lineWidth = deConfig.msScaleWidth; x2 = ox + (ringInnerR - deConfig.msSaleL) * Math.sin(radin); y2 = oy - (ringInnerR - deConfig.msSaleL) * Math.cos(radin); } ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } } //改变坐标中点,并旋转画布也许是更好的选择。 function drawHands() { var date = new Date(); var h = date.getHours() % 12; var m = date.getMinutes(); var s = date.getSeconds(); var ms = date.getMilliseconds(); // console.log(h + ":" + m + ":" + s); // 时针 var hRadin = (h + m / 60 + s / 3600) * Math.PI * 2 / 12; var mRadin = (m + s / 60) * Math.PI * 2 / 60; var sRadin; if (deConfig.handMode == 'ms') { sRadin = (s + ms / 1000) * Math.PI * 2 / 60; } else { sRadin = s * Math.PI * 2 / 60; } var x = 0; var y = 0; var hDotR = deConfig.hHandWidth + 2; var mDotR = 0.6 * hDotR var sDotR = 0.5 * hDotR //秒针半径 var sHandR = ringInnerR - deConfig.hSaleL * 2 //分针半径 var mHandR = 0.8 * sHandR; //时针半径 var hHandR = 0.7 * mHandR; //时针 ctx.beginPath(); ctx.lineWidth = deConfig.hHandWidth; ctx.strokeStyle = deConfig.hHandColor; ctx.strokeStyle = deConfig.hHandColor; ctx.moveTo(ox, oy); x = ox + hHandR * Math.cos(hRadin - Math.PI / 2); y = oy + hHandR * Math.sin(hRadin - Math.PI / 2); ctx.lineTo(x, y); ctx.stroke(); //针尖。直接圆型了(矩形指针来绘制针尖,计算复杂。。。) ctx.beginPath(); ctx.lineWidth = 0; ctx.arc(x, y, deConfig.hHandWidth / 2, 0, 2 * Math.PI); ctx.fill(); //中心 ctx.beginPath(); // ctx.lineWidth = hDotR; ctx.arc(ox, oy, hDotR / 2, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); //分针 ctx.beginPath(); ctx.lineWidth = deConfig.mHandWidth; ctx.strokeStyle = deConfig.mHandColor; ctx.fillStyle = deConfig.mHandColor; ctx.moveTo(ox, oy); x = ox + mHandR * Math.cos(mRadin - Math.PI / 2); y = oy + mHandR * Math.sin(mRadin - Math.PI / 2); ctx.lineTo(x, y); ctx.stroke(); //针尖。直接圆型了(矩形指针来绘制针尖,计算复杂。。。) ctx.beginPath(); ctx.lineWidth = 0; ctx.arc(x, y, deConfig.mHandWidth / 2, 0, 2 * Math.PI); ctx.fill(); //中心 ctx.beginPath(); ctx.arc(ox, oy, mDotR / 2, 0, Math.PI * 2); ctx.stroke(); //秒针 ctx.beginPath(); ctx.strokeStyle = deConfig.sHandColor; ctx.fillStyle = deConfig.sHandColor; ctx.lineWidth = deConfig.sHandWidth; //秒针有长短两线 x = ox - sHandR / 4 * Math.cos(sRadin - Math.PI / 2); y = oy - sHandR / 4 * Math.sin(sRadin - Math.PI / 2); ctx.moveTo(x, y); x = ox + sHandR * Math.cos(sRadin - Math.PI / 2); y = oy + sHandR * Math.sin(sRadin - Math.PI / 2); ctx.lineTo(x, y); ctx.stroke(); //针尖。直接圆型了(矩形指针来绘制针尖,计算复杂。。。) ctx.beginPath(); ctx.lineWidth = 0; ctx.arc(x, y, deConfig.sHandWidth / 2, 0, 2 * Math.PI); ctx.fill(); //中心 ctx.beginPath(); ctx.fillStyle = deColor; ctx.arc(ox, oy, sDotR, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } function beforeDraw() { if (typeof exp.beforeDraw === 'function') { exp.beforeDraw(ctx, deConfig); } } function afterDraw() { if (typeof exp.afterDraw === 'function') { exp.afterDraw(ctx, deConfig); } } var exp = { start: start, stop: stop, beforeDraw: null, afterDraw: null, } return exp; } var clockCanvas1 = document.getElementsByTagName('canvas')[0]; var clockCanvas2 = document.getElementsByTagName('canvas')[1]; var clockCanvas3 = document.getElementsByTagName('canvas')[2]; var clockCanvas4 = document.getElementsByTagName('canvas')[3]; var clock = clockHelper(clockCanvas1, {mHandColor: '#3333bb', sHandColor: '#bb3333'}); clock.start(); setTimeout(function () { clock.stop() }, 5000) setTimeout(function () { clock.start(); }, 8000) clockHelper(clockCanvas2, { mHandColor: 'green', hHandWidth: 6, mHandWidth: 4, hFontSize: 14, hScaleWidth: 2, handMode: 'ms' }).start(); clockHelper(clockCanvas2, { mHandColor: 'green', hHandWidth: 6, mHandWidth: 4, hFontSize: 14, hScaleWidth: 2, handMode: 'ms' }).start(); clockHelper(clockCanvas3, { deColor: '#bbbbbb', sHandColor: '#bbbbbb', clockFaceColor: '#112233',//钟面 hHandWidth: 6, mHandWidth: 4, hFontSize: 14, hScaleWidth: 2, handMode: 's' }).start(); var clock4 = clockHelper(clockCanvas4, { deColor: '#bbbbbb', sHandColor: '#bbbbbb', // clockFaceColor: '#112233', hHandWidth: 6, mHandWidth: 4, hFontSize: 14, hScaleWidth: 2, handMode: 's' }); clock4.afterDraw = function (ctx, deConfig) { var grd = ctx.createLinearGradient(0, 0, clockCanvas4.width, clockCanvas4.height); grd.addColorStop(0, "rgba(255,0,0,0.3)"); grd.addColorStop(1, "rgba(0,0,255,0.5)"); ctx.fillStyle = grd; ctx.arc(clockCanvas4.width/2,clockCanvas4.height/2,clockCanvas4.width/2,0,Math.PI*2); // ctx.fillRect(0, 0, clockCanvas4.width, clockCanvas4.height); ctx.fill(); ctx.fillText('时钟绘制完成后,自定义其他绘制',clockCanvas4.width/2,clockCanvas4.height - deConfig.hFontSize); }; clock4.start();</script></body></html>
说明:
1、clockHelper第一个参数传入画布。第二个参数传入时钟界面的配置对象,包括指针、刻度的颜色、大小等,配置项和clockHelper中的deConfig默认对象是相对的,参考deConfig的属性传入参数即可。
2、clockHelper的封装性略差,仅是基本能用型。但属性不多,改造应该并不困难。
3、提供了时钟界面绘制之前和之后的方法,可以在beforeDraw和afterDraw这两个方法中执行自己的逻辑。但是由于事先设计没有留出足够的空白像素,用处不大。只能进行一些简单的再绘制。比如给钟面添加色彩、渐变。
相关推荐:
Atas ialah kandungan terperinci 使用h5 canvas实现时钟的动态效果. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Panduan untuk Sempadan Jadual dalam HTML. Di sini kita membincangkan pelbagai cara untuk menentukan sempadan jadual dengan contoh Sempadan Jadual dalam HTML.

Ini ialah panduan untuk Nested Table dalam HTML. Di sini kita membincangkan cara membuat jadual dalam jadual bersama-sama dengan contoh masing-masing.

Panduan untuk HTML margin-kiri. Di sini kita membincangkan gambaran keseluruhan ringkas tentang HTML margin-left dan Contoh-contohnya bersama-sama dengan Pelaksanaan Kodnya.

Panduan untuk Susun Atur Jadual HTML. Di sini kita membincangkan Nilai Susun Atur Jadual HTML bersama-sama dengan contoh dan output n perincian.

Panduan untuk Pemegang Tempat Input HTML. Di sini kita membincangkan Contoh Pemegang Tempat Input HTML bersama-sama dengan kod dan output.

Panduan untuk Memindahkan Teks dalam HTML. Di sini kita membincangkan pengenalan, cara teg marquee berfungsi dengan sintaks dan contoh untuk dilaksanakan.

Panduan kepada Senarai Tertib HTML. Di sini kami juga membincangkan pengenalan senarai dan jenis Tertib HTML bersama-sama dengan contoh mereka masing-masing

Panduan untuk Butang onclick HTML. Di sini kita membincangkan pengenalan, kerja, contoh dan onclick Event masing-masing dalam pelbagai acara.
