在最近的 JavaScript 30天挑战中,我有机会了解 HTML 内置 canvas的特性。相对适宜的等级和学习曲线,使我写下整个过程。javascript
HTML canvas 用最简单的方式,使web 开发者可以经过JavaScript在网页上绘制图形。这样,HTML 元素变得更加有趣。css
<canvas>
元素只是个容器,你老是须要使用 JavaScript 来准确绘制图形。有人可能会说,咱们老是能够添加这些点,也能够添加SVG,但这又会多么有趣?html
回到 <canvas>
元素:canvas 在 HTML 页面上是一个矩形。canvas 是默认没有边框和内容的。java
写法像这样:git
<canvas id="canvas" width="200" height="100"></canvas>复制代码
已经作了那么多介绍,让咱们专一于使用简单的原生 JavaScript(不是很旧——ES6)作些有趣的东西。首先,咱们看下初始的文件。github
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas</title>
<link rel="stylesheeet" href="style.css" />
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="app.js"></script>
</body>
</html>复制代码
让咱们慢慢讲。咱们有个叫 style.css 的样式层叠表。而后咱们定义一个宽800和高800的 canvas 。最后,咱们在 script
标签里引用了 app.js
,全部魔法都在这里。咱们开始使用 app.js
作一些事情。web
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innnerWidth;
canvas.height = window.innerHeight;
ctx.strokeStyle = "#BADA55"
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';复制代码
canvas
的常量里。如今,咱们终于有了画布,继续定义画布的最基本属性。canvas
ctx.strokeStyle
设置或返回用于描绘的颜色,渐变色或图案。是的,你理解的对:默认颜色是 #BADASS
。ctx.lineWidth
设置或返回 当前线条的宽度。咱们把它设置为 1
,稍后咱们会讲到这个。ctx.lineJoin
设置或返回 两条线相汇时所建立的边角的类型。咱们设置当两条线交汇时的边角为圆角。ctx.lineCap
设置或返回线的端点的样式。咱们将它设置为圆形,这样当咱们不遇到其余线时,咱们仍然会获得相同的整齐的圆形,具体取决于以前定义的线的宽度。线咱们已经完成了全部这些工做,让咱们看看如何在画布上绘图。数组
首先,咱们须要为画布上的鼠标移动添加事件侦听器,而后触发一个绘制内容的函数。咱们来看看 app.js
文件中可能添加的内容。浏览器
let isDrawing = fasle;
function draw(e){
if(!isDrawing) return;
console.log(e)
}
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown', () => isDrawing = true);
canvas.addEventListener('mouseup', () => isDrawing = fasle);
canvas.addEventListener('mousedown', () => isDrawing = fasle);复制代码
咱们来说解下:
isDrawing
的变量,来判断用户是否要在画布上画图。咱们会在后面再讲到这个。draw
的函数,它会被触发,并负责完成整个绘制动做。draw
函数。经过声明 isDrawing
变量,并将其值设置为 false
,咱们在 canvas
元素被加载后设置 canvas
的初始状态,但还不绘制。而后在每一个后续事件侦听器中,咱们使用内嵌函数,并每次根据触发的事件类型更改 变量 isDrawing
变量的值。
在 draw
函数 的开头,若是 isDrawing
的值为 false
,函数会执行 return
语句。若是 isDrawing
的值为 true
, 会执行draw 函数。
咱们来扩展下 draw 函数:
let isDrawing = false;
let lastX = 0;
let lastY = 0;
function draw(e){
if(!isDrawing) return;
console.log(e);
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTO(e.offsetX,e.offsetY);
ctx.stroke();
}复制代码
lastX
,lastY
,并设置他们的初始值为 0
.MouseEvent
对象有一些很是重要和有用的属性:鼠标事件对象
咱们只关注对象里的 offsetX
和 offsetY
属性。
ctx.beginPath
,咱们新建一个路径,或者重置当前的路径。每次事件被触发,咱们希都会执行这个动做。ctx.moveTo
移动路径到画布指定的点上,但还没绘制线。在例子中,在函数外面的全局环境里定义 lastX
和 lastY
。ctx.lineTo
增长新的点,并从上一个点到新的点之间绘制一条线。ctx.stroke()
真正绘制你已经定义的路径 —— 辛苦了,伙计们!在 ctx.lineTo
里,咱们利用事件的属性 offsetX
和 offsetY
得到 在画布上的最新点的 X
和Y
,只用 ctx.lineTo
绘制一条线。
咱们几乎全部的东西都准备好了。在页面上的每一个鼠标事件都会在画布上绘制一条线——但还有些问题,没有太多酷的东西。因此,让咱们加点酷的东西。
如今,全部线都是从画布中的(0,0)
点绘制的。 咱们将其设置为加载画布或执行 draw
函数时开始绘制的初始点。
让咱们解决这个问题,以得到更好的实时体验。 若是考虑一下,答案很是简单:每次执行draw函数时,咱们都但愿初始点始终是 MouseEvent
对象的 offsetX
和 offsetY
属性。
经过使用 ES6 的数组解构,咱们能够将变量 lastX
和 lastY
分别重置为 鼠标事件对象的属性 offsetX
和 offsetY
,咱们在 draw
函数的最后执行。咱们来看看加了新东西后的 app.js
。
function draw(e){
if(!isDrawing) return;
console.log(e);
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(e.offsetX,e.offsetY);
ctx.stroke();
[lastX,lastY] = [e.offsetX,e.offsetY];
}
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown',(e) => {
isDrawing = true;
[lastX,lastY] = [e.offsetX,e.offsetY];
};
canvas.addEventListener('mouseup',()=> isDrawing = false);
canvas.addEventListener('mousemove',()=> isDrawing = false);复制代码
mousemove
事件发生时,咱们会执行 draw
函数。而后继续执行,在 draw
函数里使用 ES6 解构 设置 变量 lastX
和 lastY
。mousedown
事件发生时,首先咱们执行嵌套函数,如你所见,咱们再一次将 变量 lastX
和 lastY
设置为当前事件的偏移属性。这是为了确保当咱们在画布上从一个点移动到另外一个点时,咱们能够在画布上看到这条线。让它变得丰富多彩,并在笔画中添加一些动态元素。
let hue = 0;
let direction = true;
function draw(e){
if(!isDrawing) return;
console.log(e);
ctx.strokeStyle = `hsl(${hue},100%,50%)`;
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(e.offsetX,e.offsetY);
ctx.stroke();
[lastX,lastY] = [e.offsetX,e.offsetY];
hue++;
if(hue>=360){
hue = 0;
}
if(ctx.lineWidth >= 75 || ctx.lineWidth <= 1){
direction = !direction;
}
if(direction){
ctx.lineWidth++;
} else {
ctx.lineWidth = 1;
}
}
canvas.addEventListener('mousemove',draw);
canvas.addEventListener('mousedown',(e) => {
isDrawing = true;
[lastX,lastY] = [e.offsetX,e.offsetY];
};
canvas.addEventListener('mouseup',()=> isDrawing = false);
canvas.addEventListener('mousemove',()=> isDrawing = false);复制代码
这还有不少事要处理,咱们一一分解:
hue
,并设置其值为 0
。在其最简单的形式,hsl
让咱们在从0到360范围里使用相同的彩虹的颜色。 每一个数字都有一个亮度和透明度。 定义 hsl 看起来像这样:hsl(173,99%,50%)
。 此处 173
表示颜色 , 99%
是亮度,50%
是透明度。
一样,经过使用 ES6 的模板字符串,来修改 hsl
的值,像这样:
ctx.strokeStyle = `hsl( ${hue}, 100%, 50%)`
接下来,咱们增长 hue
变量的值,该变量在每一个mousemove
事件中更改笔触的颜色。 一旦色调值增长到360,咱们将在上述要点的第14行上将 hue
的值重置为0。 但即便咱们不这样作,咱们仍然会有一样的结果。 即使如此,咱们仍是作正确的事吧
if(hue > 360){
hue = 0
}复制代码
下一步,咱们加点动态的东西,使笔画的宽度实时变化,像这样:
if(ctx.linewidth >= 75 || ctx.lineWidth <= 1){
direction = ! direction;
}
if(direction){
ctx.lineWidth++
}else {
ctx.lineWidth = 0
}复制代码
这里咱们所作的就是首先检查当前的线宽是大于75仍是小于1。 若是是,则将初始值为 true
的变量 direction
取反。
接下来,咱们检查变量 direction
的值是否为true
。 若是是,则将 lineWidth
的值增长1
,不然将 lineWidth
重置为0
。
没有不少JavaScript。 若是你正确跟着作,你应该有个漂亮的画布了。
让咱们快速地看一下最终的文件结构是什么样子的。 由于咱们只更改了app.js
文件,因此我将只向你展现这一点,由于index.html从一开始就几乎没有变化。
canvas.width = window.innerWidth;canvas.height = window.innerHeight;ctx.strokeStyle = '#BADA55';ctx.lineWidth = 1;ctx.lineJoin = 'round';ctx.lineCap = 'round';let isDrawing = false;let lastX = 0;let lastY = 0;let hue = 0;let direction = true;function draw(e) { if(!isDrawing) { return; // 鼠标没有按下时不执行. } console.log(e); ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`; ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(e.offsetX, e.offsetY); ctx.stroke(); [lastX, lastY] = [e.offsetX, e.offsetY]; hue++; if(hue>=360){ hue = 0; } if(ctx.lineWidth >= 75 || ctx.lineWidth <= 1) { direction = !direction; } if(direction){ ctx.lineWidth++; } else { ctx.lineWidth = 1; }}canvas.addEventListener('mousemove', draw);canvas.addEventListener('mousedown', (e) => { isDrawing = true; [lastX, lastY] = [e.offsetX, e.offsetY];});canvas.addEventListener('mouseup', ()=> isDrawing = false);canvas.addEventListener('mouseout', () => isDrawing = false);复制代码
在 canvas 和 JavaScript 的结合中,介绍的只是冰山一角。 我会鼓励你作更多的研究,使画布看起来更好。 也许添加几个按钮来清除屏幕,或者选择一种特定的颜色在画布上绘制。 有不少选择!