【译】用一天入门 canvas 和 JavaScript - 图1

在最近的 JavaScript 30天挑战中,我有机会了解 HTML 内置 canvas的特性。相对适宜的等级和学习曲线,使我写下整个过程。

HTML canvas 用最简单的方式,使web 开发者能够通过JavaScript在网页上绘制图形。这样,HTML 元素变得更加有趣。

<canvas>元素只是个容器,你总是需要使用 JavaScript 来准确绘制图形。有人可能会说,我们总是可以添加这些点,也可以添加SVG,但这又会多么有趣?

回到<canvas>元素:canvas 在 HTML 页面上是一个矩形。canvas 是默认没有边框和内容的。

写法像这样:

  1. <canvas id="canvas" width="200" height="100"></canvas>

开始

已经做了那么多介绍,让我们专注于使用简单的原生 JavaScript(不是很旧——ES6)做些有趣的东西。首先,我们看下初始的文件。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>HTML5 Canvas</title>
  6. <link rel="stylesheeet" href="style.css" />
  7. </head>
  8. <body>
  9. <canvas id="canvas" width="800" height="800"></canvas>
  10. <script src="app.js"></script>
  11. </body>
  12. </html>

让我们慢慢讲。我们有个叫 style.css 的样式层叠表。然后我们定义一个宽800和高800的 canvas 。最后,我们在 script 标签里引用了 app.js,所有魔法都在这里。我们开始使用 app.js 做一些事情。

  1. const canvas = document.querySelector('#canvas');
  2. const ctx = canvas.getContext('2d');
  3. canvas.width = window.innnerWidth;
  4. canvas.height = window.innerHeight;
  5. ctx.strokeStyle = "#BADA55"
  6. ctx.lineWidth = 1;
  7. ctx.lineJoin = 'round';
  8. ctx.lineCap = 'round';
  • 我们一开始,在第一行选择 canvas 元素保存 到 叫 canvas 的常量里。
  • 然后,我们在同一个 canvas 中获取2D上下文,并保存到指定的变量中。
  • 设置 canvas 的宽度和高度,分别等于 窗口的宽度和高度。

现在,我们终于有了画布,继续定义画布的最基本属性。

  • ctx.strokeStyle设置或返回用于描绘的颜色,渐变色或图案。是的,你理解的对:默认颜色是 #BADASS。
  • ctx.lineWidth 设置或返回 当前线条的宽度。我们把它设置为 1,稍后我们会讲到这个。
  • ctx.lineJoin 设置或返回 两条线相汇时所创建的边角的类型。我们设置当两条线交汇时的边角为圆角。
  • ctx.lineCap设置或返回线的端点的样式。我们将它设置为圆形,这样当我们不遇到其他线时,我们仍然会得到相同的整齐的圆形,具体取决于之前定义的线的宽度。

线我们已经完成了所有这些工作,让我们看看如何在画布上绘图。

首先,我们需要为画布上的鼠标移动添加事件侦听器,然后触发一个绘制内容的函数。我们来看看 app.js 文件中可能添加的内容。

  1. let isDrawing = fasle;
  2. function draw(e){
  3. if(!isDrawing) return;
  4. console.log(e)
  5. }
  6. canvas.addEventListener('mousemove',draw);
  7. canvas.addEventListener('mousedown', () => isDrawing = true);
  8. canvas.addEventListener('mouseup', () => isDrawing = fasle);
  9. canvas.addEventListener('mousedown', () => isDrawing = fasle);

我们来讲解下:

  • 我们开始定义一个叫 isDrawing 的变量,来判断用户是否要在画布上画图。我们会在后面再讲到这个。
  • 现在,我们有一个叫 draw 的函数,它会被触发,并负责完成整个绘制动作。
  • 最后,我们增加一个约束条件,确保我们捕获正确的事件,并且只在需要的时候执行 draw 函数。

通过声明 isDrawing 变量,并将其值设置为 false,我们在 canvas 元素被加载后设置 canvas 的初始状态,但还不绘制。然后在每个后续事件侦听器中,我们使用内嵌函数,并每次根据触发的事件类型更改 变量 isDrawing 变量的值。

在 draw 函数 的开头,如果 isDrawing 的值为 false ,函数会执行 return 语句。如果 isDrawing 的值为 true, 会执行draw 函数。

绘制函数

我们来扩展下 draw 函数:

  1. let isDrawing = false;
  2. let lastX = 0;
  3. let lastY = 0;
  4. function draw(e){
  5. if(!isDrawing) return;
  6. console.log(e);
  7. ctx.beginPath();
  8. ctx.moveTo(lastX,lastY);
  9. ctx.lineTO(e.offsetX,e.offsetY);
  10. ctx.stroke();
  11. }
  • 我们在开头定义两个全局变量:lastX,lastY,并设置他们的初始值为 0.
  • 如果你现在进入浏览器的控制台,你会发现你已经记录了所有鼠标移动。这个 MouseEvent 对象有一些非常重要和有用的属性:
  • 【译】用一天入门 canvas 和 JavaScript - 图2

    1. 鼠标事件对象

我们只关注对象里的 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。

  1. function draw(e){
  2. if(!isDrawing) return;
  3. console.log(e);
  4. ctx.beginPath();
  5. ctx.moveTo(lastX,lastY);
  6. ctx.lineTo(e.offsetX,e.offsetY);
  7. ctx.stroke();
  8. [lastX,lastY] = [e.offsetX,e.offsetY];
  9. }
  10. canvas.addEventListener('mousemove',draw);
  11. canvas.addEventListener('mousedown',(e) => {
  12. isDrawing = true;
  13. [lastX,lastY] = [e.offsetX,e.offsetY];
  14. };
  15. canvas.addEventListener('mouseup',()=> isDrawing = false);
  16. canvas.addEventListener('mousemove',()=> isDrawing = false);
  • 当 mousemove 事件发生时,我们会执行 draw 函数。然后继续执行,在 draw 函数里使用 ES6 解构 设置 变量 lastX 和 lastY。
  • 当 mousedown 事件发生时,首先我们执行嵌套函数,如你所见,我们再一次将 变量 lastX 和 lastY 设置为当前事件的偏移属性。这是为了确保当我们在画布上从一个点移动到另一个点时,我们可以在画布上看到这条线。

让它变得丰富多彩,并在笔画中添加一些动态元素。

  1. let hue = 0;
  2. let direction = true;
  3. function draw(e){
  4. if(!isDrawing) return;
  5. console.log(e);
  6. ctx.strokeStyle = `hsl(${hue},100%,50%)`;
  7. ctx.beginPath();
  8. ctx.moveTo(lastX,lastY);
  9. ctx.lineTo(e.offsetX,e.offsetY);
  10. ctx.stroke();
  11. [lastX,lastY] = [e.offsetX,e.offsetY];
  12. hue++;
  13. if(hue>=360){
  14. hue = 0;
  15. }
  16. if(ctx.lineWidth >= 75 || ctx.lineWidth <= 1){
  17. direction = !direction;
  18. }
  19. if(direction){
  20. ctx.lineWidth++;
  21. } else {
  22. ctx.lineWidth = 1;
  23. }
  24. }
  25. canvas.addEventListener('mousemove',draw);
  26. canvas.addEventListener('mousedown',(e) => {
  27. isDrawing = true;
  28. [lastX,lastY] = [e.offsetX,e.offsetY];
  29. };
  30. canvas.addEventListener('mouseup',()=> isDrawing = false);
  31. 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。 但即使我们不这样做,我们仍然会有同样的结果。 即便如此,我们还是做正确的事吧

  1. if(hue > 360){
  2. hue = 0
  3. }

下一步,我们加点动态的东西,使笔画的宽度实时变化,像这样:

  1. if(ctx.linewidth >= 75 || ctx.lineWidth <= 1){
  2. direction = ! direction;
  3. }
  4. if(direction){
  5. ctx.lineWidth++
  6. }else {
  7. ctx.lineWidth = 0
  8. }

这里我们所做的就是首先检查当前的线宽是大于75还是小于1。 如果是,则将初始值为 true 的变量 direction 取反。

接下来,我们检查变量 direction 的值是否为true。 如果是,则将 lineWidth 的值增加1,否则将 lineWidth 重置为0。

没有很多JavaScript。 如果你正确跟着做,你应该有个漂亮的画布了。

让我们快速地看一下最终的文件结构是什么样子的。 因为我们只更改了app.js文件,所以我将只向你展示这一点,因为index.html从一开始就几乎没有变化。

  1. canvas.width = window.innerWidth;
  2. canvas.height = window.innerHeight;
  3. ctx.strokeStyle = '#BADA55';
  4. ctx.lineWidth = 1;
  5. ctx.lineJoin = 'round';
  6. ctx.lineCap = 'round';l
  7. et isDrawing = false;
  8. let lastX = 0;
  9. let lastY = 0;
  10. let hue = 0;
  11. let direction = true;
  12. function draw(e) {
  13. if(!isDrawing) {
  14. return; // 鼠标没有按下时不执行.
  15. }
  16. console.log(e);
  17. ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
  18. ctx.beginPath();
  19. ctx.moveTo(lastX, lastY);
  20. ctx.lineTo(e.offsetX, e.offsetY);
  21. ctx.stroke();
  22. [lastX, lastY] = [e.offsetX, e.offsetY];
  23. hue++;
  24. if(hue>=360){
  25. hue = 0;
  26. }
  27. if(ctx.lineWidth >= 75 || ctx.lineWidth <= 1) {
  28. direction = !direction;
  29. }
  30. if(direction){
  31. ctx.lineWidth++;
  32. } else {
  33. ctx.lineWidth = 1;
  34. }}
  35. canvas.addEventListener('mousemove', draw);
  36. canvas.addEventListener('mousedown', (e) => {
  37. isDrawing = true;
  38. [lastX, lastY] = [e.offsetX, e.offsetY];});
  39. canvas.addEventListener('mouseup', ()=> isDrawing = false);
  40. canvas.addEventListener('mouseout', () => isDrawing = false);

在 canvas 和 JavaScript 的结合中,介绍的只是冰山一角。 我会鼓励你做更多的研究,使画布看起来更好。 也许添加几个按钮来清除屏幕,或者选择一种特定的颜色在画布上绘制。 有很多选择!