Canvas API 供应了一个通过 JavaScript 和 HTML 的 <canvas> 元向来绘制图形的办法。
它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

<canvas>标签本身没有绘图能力,它仅仅是图形的容器。
在HTML,一样平常通过Javascript措辞来完成实际的操作。

创建HTML页面并添加绘图容器

本文通过Javascript操作Canvas制作一个大略的显示当前韶光的动画时钟,理解和学习大略的canvas用法,仅以抛砖引玉。

html做电子数字时钟HTML应用Canvas绘制动画时钟 GraphQL

首先创建一个HTML文件,为了方便管理,利用一个div标签包裹两个canvas标签,并加上一些大略的css样式。

<!doctype html><html lang="zh-cn"><head><title>Canvas绘制动画时钟</title><style>html,body{margin:0;padding:0}#clockWrap {position: relative;}canvas {position: absolute;}#clock-ui {z-index: 2;}#clock-plate {z-index: 1;}</style></head><body> <div id="clockWrap"> <canvas id="clock-plate"></canvas> <canvas id="clock-ui"></canvas></div><script></script></body></html>

本示例中利用了两个canvas标签(为什么利用两个,一个不是更大略吗?),一个用于绘制钟面,一个根据当前韶光实时显示和更新时针、分针和秒针的动态指向。
好了,话不多说,开干。

绘制钟面刻度

一个大略的时钟,可以分为钟面上的刻度和指针。
个中刻度和12个数字是固定的,我们可以将它们绘制在当作背景的canvas上(示例中id为clock-plate的canvas)。

(1)要利用canvas,首先必须通过容器获取渲染高下文:

var $=function(id){return document.querySelector(id);}//这个函数只是为了方便获取dom元素const canvasbg=$("#clock-plate"), canvas=$("#clock-ui"), ctx = canvasbg.getContext("2d"),//背景容器高下文 ctxUI = canvas.getContext("2d");//指针容器高下文,后面代码要用//定义画布宽度和高度,时钟圆直径,并设置画布大小const oW=1000,oH=800,cW=400,r=cW/2,oX=oW/2,oY=oH/2;canvas.width=oW;canvas.height=oH;canvasbg.width=oW;canvasbg.height=oH;

(2)画钟的边框,为了好看,这里画两个圈:

//画出时钟外圆框 ctx.lineWidth = 12;ctx.beginPath();ctx.arc(oX, oY, r+14, 0, 2 Math.PI);ctx.stroke();ctx.closePath();ctx.lineWidth = 8;//画出时钟内圆框(刻度圈)ctx.beginPath();ctx.arc(oX, oY, r, 0, 2 Math.PI);ctx.stroke();ctx.closePath();ctx.beginPath();

边框效果图

(3)绘制刻度线和数字,可以利用三角函数打算出每个刻度的坐标:

利用三角函数打算刻度线的坐标位置

钟面上有12个大格,从正上方12开始,它们的度数分别是270,300,330,0,30,60,90,120,150,180,210,240。
然后利用JS的Math.sin和Math.cos分别打算出各大格的坐标。
把稳:js中Math.sin()和Math.cos()的参数不是角度数是弧度。
可以利用Math.PI/180角度来转化,比如将30度转换成弧度=Math.PI/18030

//绘制钟表中央点ctx.beginPath();ctx.arc(oX, oY, 8, 0, 2 Math.PI);//圆心ctx.fill();ctx.closePath();//设置刻度线粗细度ctx.lineWidth = 3;//设置钟面12个数字的字体、大小和对齐办法ctx.font = "30px serif";ctx.textAlign="center";ctx.textBaseline="middle";var kdx,kdy;//绘制12个大刻度和12个数字//为方便打算,先定义了0-11这12个刻度对应的度数,也可以直接定义对应的弧度。
const hd=Math.PI/180,degr=[270,300,330,0,30,60,90,120,150,180,210,240];for(var i=0;i<12;i++){kdx=oX+Math.cos(hddegr[i])(r-3);kdy=oY+Math.sin(hddegr[i])(r-3);ctx.beginPath();ctx.arc(kdx, kdy, 6, 0, 2 Math.PI);//画圆形大刻度ctx.fill(); //绘制刻度对应的数字ctx.strokeText(i==0? 12 : i,oX+Math.cos(hddegr[i])(r-24),oY+Math.sin(hddegr[i])(r-24));ctx.closePath();}//绘制小刻度ctx.lineWidth = 2;for(var i=0;i<60;i++){if(i % 5 == 0) continue;//跳过与刻度重叠的刻度x0=Math.cos(hdi6);y0=Math.sin(hdi6);ctx.beginPath();ctx.moveTo(oX+x0(r-10), oY+y0(r-10)); ctx.lineTo(oX+x0r, oY+y0r); //画短刻度线ctx.stroke(); ctx.closePath();}

效果如图:

钟面效果图

(4)根据当前韶光绘制指针

习气上,时针粗短,分针略粗而长,秒针苗条。
为加大差异,示例中秒针苗条并且绘制成赤色。

function drawHp(i){//绘制时针const x0=Math.cos(hdi30),y0=Math.sin(hdi30);drawPointer(oX,oY,oX+x0(r-90),oY+y0(r-90),10,"#000000");}function drawMp(i){//绘制分针const x0=Math.cos(hdi6),y0=Math.sin(hdi6);drawPointer(oX,oY,oX+x0(r-60),oY+y0(r-60),5,"#000000");}function drawSp(i){//绘制秒针const x0=Math.cos(hdi6),y0=Math.sin(hdi6);drawPointer(oX,oY,oX+x0(r-20),oY+y0(r-20),2,"#FF0000");}//抽取出绘制三种指针时共同的部分,把稳指针绘制在渲染高下文ctxUI中function drawPointer(ox,oy,tx,ty,width,color){ctxUI.strokeStyle = color;ctxUI.lineCap = "round";ctxUI.lineWidth = width;ctxUI.beginPath();ctxUI.moveTo(ox, oy);ctxUI.lineTo(tx,ty);ctxUI.stroke();ctxUI.closePath();}

现在已经有了绘制三种指针的方法,参数是当前韶光的时、分和秒,将根据它们的值确定指针的坐标。
不过,由于利用的是默认的convas坐标体系,0值实际指向3的位置,须要小小的改动一下。

window.requestAnimationFrame(function fn(){var d = new Date();ctxUI.clearRect(0,0,oW,oH);//度数从0开始,而0在3刻度(15分/秒位置),改动为全值减15,如果小于0则改动回来 var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;hour=hour>11? hour-15 : hour-3;drawHp(hour>=0? hour : 12+hour);drawMp(minute>=0? minute : 60+minute);drawSp(second>=0? second : 60+second);window.requestAnimationFrame(fn);});

接下来,调用window.requestAnimationFrame,在个中绘制并更新指标的位置。
看看效果如何:

指针绘制情形与实际韶光符合

貌似效果有了,截图时电脑上的韶光是10时17分,指针绘制上,时针指向10时,分针指向17。
嗯,觉得有点别扭?对了,时针和分针怎么是正直直正地指向它们的整时整分刻度上呢?实际钟表上时针和分针是展示动态进度的,此时时针该当超越10时的位置才对。
没紧要,我们在绘制时针和分针时别点东西,让它的角度值加上分针和秒针的值试试。

//修正后的绘制三种指针的方法function drawHp(i,f,m){//绘制时针,参数:时,分,秒const x0=Math.cos(hd(i+(f/60)+(m/600))30),y0=Math.sin(hd(i+(f/60)+(m/600))30);drawPointer(oX,oY,oX+x0(r-90),oY+y0(r-90),10,"#000000");}function drawMp(i,f){//绘制分针,参数,分,秒const x0=Math.cos(hd(i+(f/60))6),y0=Math.sin(hd(i+(f/60))6);drawPointer(oX,oY,oX+x0(r-60),oY+y0(r-60),5,"#000000");}function drawSp(i){//绘制秒针const x0=Math.cos(hdi6),y0=Math.sin(hdi6);drawPointer(oX,oY,oX+x0(r-20),oY+y0(r-20),2,"#FF0000");}

再来看看效果,嗯,吹糠见米呀:

指针指向更合理了

到此为止,canvas绘制一个大略单纯时钟就完成了。
下面连续优化一下。
刚才利用requestAnimationFrame方法即时更新绘制情形。
这个方法与刷新率有关,看看mdn上面怎么说的:

window.requestAnimationFrame() 方法会见告浏览器你希望实行一个动画。
它哀求浏览器不才一次重绘之前,调用用户供应的回调函数。

对回调函数的调用频率常日与显示器的刷新率相匹配。
虽然 75hz、120hz 和 144hz 也被广泛利用,但是最常见的刷新率还是 60hz(每秒 60 个周期/帧)。
为了提高性能和电池寿命,大多数浏览器都会停息在后台选项卡或者隐蔽的 <iframe> 中运行的 requestAnimationFrame()。

本示例中,更新指针的位置并不须要很高的刷新频率,可以通过节流进行一下优化:

var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //记录上次实行的韶光function runStep() { requestAnimationFrame(runStep); var d=new Date(),now = d.getTime() var elapsed = now - lastTime; if (elapsed > fpsInterval) {ctxUI.clearRect(0,0,oW,oH); lastTime = now - (elapsed % fpsInterval); //度数从0开始,而0在3刻度(15分/秒位置),改动为全值-15,如果小于0则用60减回 var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;//console.log(d.getSeconds(),second); hour=hour>11? hour-15 : hour-3; drawHp(hour>=0? hour : 12+hour,minute+15,second+15); drawMp(minute>=0? minute : 60+minute,second+15); drawSp(second>=0? second : 60+second); }}runStep();

当然,实现时钟的方法是很多,比如可以利用画布的旋转(rotate方法)来实现指针的动态迁徙改变等等。

完全HTML+JS源码:

<!doctype html><html lang="zh-cn"><head><title>Canvas实现时钟</title><style>html,body{margin:0;padding:0}#clockWrap {position: relative;}canvas {position: absolute;}#clock-ui {z-index: 2;}#clock-plate {z-index: 1;}</style></head><body><div id="clockWrap"><canvas id="clock-plate"></canvas><canvas id="clock-ui"></canvas></div><script>var $=function(id){return document.querySelector(id);}const canvasbg=$("#clock-plate"),canvas=$("#clock-ui"),ctx = canvasbg.getContext("2d"),ctxUI = canvas.getContext("2d"),oW=1000,oH=800,cW=400,r=cW/2,oX=oW/2,oY=oH/2;canvas.width=oW;canvas.height=oH;canvasbg.width=oW;canvasbg.height=oH;const hd = Math.PI/180;function drawClockFace(){ctx.lineWidth = 12;ctx.beginPath();ctx.arc(oX, oY, r+14, 0, 2 Math.PI);ctx.stroke();ctx.closePath();ctx.lineWidth = 8;ctx.beginPath();ctx.arc(oX, oY, r, 0, 2 Math.PI);ctx.stroke();ctx.closePath();//绘制钟表中央点ctx.beginPath();ctx.arc(oX, oY, 8, 0, 2 Math.PI);//圆心ctx.fill();ctx.closePath();//设置刻度线粗细度ctx.lineWidth = 3;//设置钟面12个数字的字体、大小和对齐办法ctx.font = "30px serif";ctx.textAlign="center";ctx.textBaseline="middle";var kdx,kdy;//绘制12个大刻度和12个数字const degr=[270,300,330,0,30,60,90,120,150,180,210,240]for(var i=0;i<12;i++){kdx=oX+Math.cos(hddegr[i])(r-3);kdy=oY+Math.sin(hddegr[i])(r-3);ctx.beginPath();ctx.arc(kdx, kdy, 6, 0, 2 Math.PI);ctx.fill();ctx.strokeText(i==0? 12 : i,oX+Math.cos(hddegr[i])(r-24),oY+Math.sin(hddegr[i])(r-24));ctx.closePath();}//绘制小刻度ctx.lineWidth = 2;for(var i=0;i<60;i++){if(i % 5 == 0) continue;//跳过与刻度重叠的刻度x0=Math.cos(hdi6);y0=Math.sin(hdi6);ctx.beginPath();ctx.moveTo(oX+x0(r-10), oY+y0(r-10)); ctx.lineTo(oX+x0r, oY+y0r); ctx.stroke(); ctx.closePath();}}drawClockFace();var fps = 5, fpsInterval = 1000 / fps,lastTime = new Date().getTime(); //记录上次实行的韶光function runStep() { requestAnimationFrame(runStep); var d=new Date(),now = d.getTime() var elapsed = now - lastTime; if (elapsed > fpsInterval) {ctxUI.clearRect(0,0,oW,oH); lastTime = now - (elapsed % fpsInterval);//度数从0开始,而0在3刻度(15分/秒位置),改动为全值-15,如果小于0则用60减回 var hour=d.getHours(),minute=d.getMinutes()-15,second=d.getSeconds()-15;//console.log(d.getSeconds(),second);hour=hour>11? hour-15 : hour-3;drawHp(hour>=0? hour : 12+hour,minute+15,second+15);drawMp(minute>=0? minute : 60+minute,second+15);drawSp(second>=0? second : 60+second); }}runStep();function drawHp(i,f,m){//绘制时针const x0=Math.cos(hd(i+(f/60)+(m/600))30),y0=Math.sin(hd(i+(f/60)+(m/600))30);drawPointer(oX,oY,oX+x0(r-90),oY+y0(r-90),10,"#000000");}function drawMp(i,f){//绘制分针const x0=Math.cos(hd(i+(f/60))6),y0=Math.sin(hd(i+(f/60))6);drawPointer(oX,oY,oX+x0(r-60),oY+y0(r-60),5,"#000000");}function drawSp(i){//绘制秒针const x0=Math.cos(hdi6),y0=Math.sin(hdi6);drawPointer(oX,oY,oX+x0(r-20),oY+y0(r-20),2,"#FF0000");}function drawPointer(ox,oy,tx,ty,width,color){ctxUI.strokeStyle = color;ctxUI.lineCap = "round";ctxUI.lineWidth = width;ctxUI.beginPath();ctxUI.moveTo(ox, oy);ctxUI.lineTo(tx,ty);ctxUI.stroke();ctxUI.closePath();}</script></body></html>