【译】用一天入门 canvas 和 JavaScript

1,496 阅读7分钟

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

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

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

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

写法像这样:

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

开始

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

<!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 做一些事情。

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 的常量里。
  • 然后,我们在同一个 canvas 中获取2D上下文,并保存到指定的变量中。
  • 设置 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();
}

  • 我们在开头定义两个全局变量:lastXlastY,并设置他们的初始值为 0.
  • 如果你现在进入浏览器的控制台,你会发现你已经记录了所有鼠标移动。这个 MouseEvent 对象有一些非常重要和有用的属性:

                                               鼠标事件对象

我们只关注对象里的 offsetXoffsetY 属性。

  • 每次执行 ctx.beginPath ,我们新建一个路径,或者重置当前的路径。每次事件被触发,我们希都会执行这个动作。
  • ctx.moveTo 移动路径到画布指定的点上,但还没绘制线。在例子中,在函数外面的全局环境里定义 lastXlastY
  • ctx.lineTo 增加新的点,并从上一个点到新的点之间绘制一条线。
  • ctx.stroke() 真正绘制你已经定义的路径 —— 辛苦了,伙计们!

ctx.lineTo 里,我们利用事件的属性 offsetXoffsetY 获得 在画布上的最新点的 XY ,只用 ctx.lineTo 绘制一条线。

我们几乎所有的东西都准备好了。在页面上的每个鼠标事件都会在画布上绘制一条线——但还有些问题,没有太多酷的东西。所以,让我们加点酷的东西。

酷!

现在,所有线都是从画布中的(0,0)点绘制的。 我们将其设置为加载画布或执行 draw 函数时开始绘制的初始点。

让我们解决这个问题,以获得更好的实时体验。 如果考虑一下,答案非常简单:每次执行draw函数时,我们都希望初始点始终是 MouseEvent 对象的 offsetXoffsetY 属性。

通过使用 ES6 的数组解构,我们可以将变量 lastXlastY 分别重置为 鼠标事件对象的属性 offsetXoffsetY,我们在 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 解构 设置 变量 lastXlastY
  • mousedown 事件发生时,首先我们执行嵌套函数,如你所见,我们再一次将 变量 lastXlastY 设置为当前事件的偏移属性。这是为了确保当我们在画布上从一个点移动到另一个点时,我们可以在画布上看到这条线。

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

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