今天这时代,科技发展得飞快,我们都喜欢既美观又实用的小玩意儿。就拿动态时钟来说,在网上啊、手机APP里头都能找到,一看就能知道时间!本篇教程就教你怎么用Canvas画个活生生的时钟,咱们要用到定时器让它动起来,更有真实感!
刻度的绘制
要画个动感十足的钟面,关键就是得把刻度弄明白了。Canvas用起来超方便,只要让它旋转表盘,再用圆的坐标公式和三角函数sin、cos搞定每个刻度,保证每时每刻都在表盘上乖乖滴跑起来。这下,无论是小时还是分钟,都可以跟随着时间不停跑咯~
文字环绕的挑战
,把字弄进圆圈可不是件容易事儿!要考虑字下缘跟对齐的问题,还有角度偏移,让字正中圈心才行。不过Canvas API好像量不了字高只有字宽,那就有点麻烦了,只能靠慢慢调整Y轴方位找理想效果。
文字居中显示
说到“让”字放中间这件事,我们其实还有其他方法可以试下的。比如说,我们可以设个基准线然后把对齐方式调到居中,这样就能够尽可能地减小误差。或者,给x坐标那个数值添加上行宽度的一半,然后向左稍微移动一点,也能达到水平居中的效果嘞。至于哪个方法更好使,可能最终还得看具体情况毕竟咱们的主要目的就是为了把字排版得好看些~
时钟预览与误差处理
用动态时钟,偶尔会有些小失误,比如时间显示和真实的有些出入。但别担心,我们可以提前想办法解决。比如,让代码里的计时器帮咱们定期刷整个画布画面,这样就能随时更新时间,而且时钟还是动态的!
主要知识点与参考图
要画动态时钟,关键在于圆的坐标公式、三角函数sin、cos的运算。实际上,不用坐标公式问题也不大复杂了可不利于操作!但sin、cos可得好好学学它们在调整刻度和字号方面作用很大~
代码编写与改造
时钟 <canvas width="300" height="300">
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();
搞定桌面上动态时钟后,咱们可以再来搞搞代码,让它更加强大。把那些老是重复出现的部分整理一下,用户用起来会更顺手。在beforeDraw和afterDraw这两个方法中加些自己的创意,使得功能更加丰富多样,再说看看能不能稍作优化,让程序跑得快点么!
别小看在Canvas上画个动感时钟,这可是门大学问!学习的过程中,要聪明地想办法和耐心地试错,遇到难题也千万别放弃。只要勇敢去学、去动手,不怕挑战,我们肯定能提升技能,做出更炫酷的动态时钟!
评论0