Canvas时钟demo及API说明
此篇博客主要通过绘制时钟demo分享一下Canvas API及个人理解误区。
HTML5 canvas和学习 HTML5 Canvas 这一篇文章就够了主要参考了这两篇博客,收获颇丰值得推荐。
下边结合个人理解,绘制一个钟表demo作为演示,主要逻辑会在下面完整demo代码中备注说明;
canvas是HTML5新增标签,只定义图形,绘制图形需要利用API及JS来实现;可以进行画线 图形等自定义路径进行绘制;
一. 起步
1.创建
由于某些较老的浏览器(尤其是 IE9 之前的 IE 浏览器)不支持canvas元素,可以在标签中声明替代内容做一下兼容处理。支持canvas的浏览器会只渲染 标签,而忽略其中的替代内容。不支持canvas的浏览器则会直接渲染替代内容。
// 声明canvas
<canvas id="canvas" width="800" height="600">您的浏览器不支持Canvas,请升级或更换浏览器!</canvas>
注意:
1.标签通常需要指定一个id属性方便引用, 元素默认大小是300*150,单位px;宽高需要使用width和height属性声明,而不是css样式控制,行内样式和内联样式都是不对的,会拉伸画布造成绘制的图形跟预想的并不一样。
2.理解绘制
绘制图形分为以下4步骤,根据需求绘制每一帧画布,再指定顺序播放每一帧播放也就动起来变成了动画。
- 清除画布 clearRect()
- 保存状态 save()
- 绘制
- 恢复状态 restore()
以绘制表盘刻度为例,每次绘制前先save()保存当前绘制状态,旋转画布绘制结束后再restore()恢复到之前保存的状态,绘制此帧动画结束。进入下一次循环绘制下一帧也是同理。
// 刻度
for (let i = 0; i < 60; i++) {
ctx.save();
ctx.beginPath();
ctx.rotate(-PI / 2 + ((2 * PI) / 60) * i);
ctx.moveTo(290, 0);
ctx.lineTo(300, 0);
ctx.strokeStyle = "#fff";
ctx.lineWidth = 1;
ctx.stroke();
ctx.restore();
}
注意:
1.canvas是基于状态进行绘制的,在每一次进行绘制时,canvas不是简单的将上一段代码进行绘制,而是检查在整个程序中设置的所有状态,基于这些状态完成一次绘制。这一点需要理解(参考学习 HTML5 Canvas 这一篇文章就够了)。所以才会有save()、restore()、beginPath()等。
2.补充一下,beginPath()与closePath()没有必然联系,不是必须成对存在。beginPath是新建路径,closePath的意思不是结束路径,而是关闭路径,它会试图从当前路径起点和终点之间创建一条路径,让整个路径闭合起来。简单举例用线段绘制了一个L,在声明closePath后,画布会自动闭合这个绘制路径为一个三角形就是这个意思;
示例demo
以下是一个自适应父级宽高的demo;每一步的逻辑都已经解释清楚,注释标注才是我最想说明的部分,理解这个逻辑,这个demo非常容易写出来。
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
#wrap {
width: 80vw;
height: 80vh;
border: 2px solid #000;
}
#canvas {
background: #000;
}
</style>
<div id="wrap">
<canvas id="canvas">您的浏览器不支持Canvas,请升级或更换浏览器!</canvas>
</div>
// 获取包裹宽高
var wrap = document.getElementById("wrap"),
wrap_style = window.getComputedStyle(wrap),
wrap_width = wrap_style.width.split("px")[0],
wrap_height = wrap_style.height.split("px")[0];
// 创建画布
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
PI = Math.PI;
window.onload = function () {
init();
};
// 初始化
function init() {
//判断是否支持canvas
if (!ctx) {
alert("您的浏览器不支持Canvas,请升级或更换浏览器!");
return;
}
// 将父容器的宽高赋值给canvas宽高属性,适应父级宽高
canvas.width = wrap_width;
canvas.height = wrap_height;
drawClock();
}
// 绘制表盘
function drawClock() {
// 绘制4大步骤,在这里具体实现
// 1.清除画布
ctx.clearRect(0, 0, wrap_width, wrap_height);
//2.保存状态&位移原点到画布中心
ctx.save();
ctx.translate(wrap_width / 2, wrap_height / 2);
//3.绘制部分
//表盘
ctx.beginPath();
ctx.arc(0, 0, 300, 0, Math.PI * 2);
ctx.strokeStyle = "rgba(255,255,255,1)";
ctx.stroke();
// 刻度,绘制每一个刻度后返回原点.旋转到指定位置绘制下一个刻度
for (let i = 0; i < 60; i++) {
ctx.save();
ctx.beginPath();
ctx.rotate(-PI / 2 + ((2 * PI) / 60) * i);
ctx.moveTo(290, 0);
ctx.lineTo(300, 0);
ctx.strokeStyle = i % 5 === 0 ? "#fff" : "rgba(255,255,255,0.5)";
ctx.lineWidth = i % 5 === 0 ? 3 : 1;
ctx.stroke();
ctx.restore();
}
// 绘制每一帧 时针 分针 秒针
drawAllHands();
// 4.恢复上一次保存状态
ctx.restore();
// 5.requestAnimationFrame 递归调用实现动画
window.requestAnimationFrame(drawClock);
}
// 绘制时分秒
function drawAllHands() {
var date = new Date(),
h = date.getHours(),
m = date.getMinutes(),
s = date.getSeconds();
// 注意区分每一刻度 时分秒代表的时间
var s_angle = ((2 * PI) / 60) * s;
var m_angle = ((2 * PI) / 60) * m + s_angle / 60;
var h_angle = ((2 * PI) / 12) * h + m_angle / 12;
drawHand(s_angle, -30, 290, "#fff");
drawHand(m_angle, -20, 250, "green", 4);
drawHand(h_angle, -10, 180, "red", 8);
}
// 绘制指针
function drawHand(angele, start, end, color, width) {
ctx.save();
ctx.beginPath();
ctx.rotate(-PI / 2 + angele);
ctx.moveTo(start, 0);
ctx.lineTo(end, 0);
ctx.strokeStyle = color || "#fff";
ctx.lineWidth = width || 1;
ctx.lineCap = "round" || "butt";
ctx.stroke();
ctx.restore();
}
有问题欢迎指正,分享请注明出处。