JavaScript 笔记(十一):Canvas

JavaScript 笔记(十一):Canvas

介绍

canvas 标签是 HTML5 新增的标签,可以使用 JavaScript 在此标签上绘制各种图形,canvas 相应的 DOM 元素有绘制路径、矩形、圆形、字符以及图片的方法,如下是绘制直线的一个示例:

<style>
    * {
        margin: 0;
        padding: 0;
    }

    canvas {
        display: block;
        border: 1px solid black;
        box-shadow: 0 0 10px black;
        margin: 100px auto;
    }
</style>
<canvas></canvas>   <!-- 1. 创建 canvas 元素 -->
<script>
    /* 2. DOM 元素 */
    let oCanvas = document.querySelector("canvas");
    /* 3. 绘图工具 */
    let ctx = oCanvas.getContext("2d");
    /* 4. 绘图 */
    ctx.moveTo(50, 50); // 4.1 起始点
    ctx.lineTo(80, 50); // 4.2 终点
    ctx.stroke();       // 4.3 相连始末
</script>

canvas 标签默认是行内块级元素,且有默认的宽度(300px)以及高度(150px),此外,必须注意如下内容:

  • 不能通过 CSS 修改 canvas 元素的宽高(破坏内容),必须 canvas 标签的行内属性(width / height)
  • 默认情况下,canvas 标签中线条为 1px、黑色,而在实际情况下,默认将线条的中心点与像素的底部对齐,从而导致线条为 2px、灰色

线条

在 canvas 中绘制线条时,可以设置线条的宽度、颜色以及样式,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");
ctx.moveTo(50, 50);
ctx.lineTo(80, 50);
ctx.lineWidth = 10; // 线条宽度
ctx.strokeStyle = "orangered";  // 线条颜色
ctx.lineCap = "round";  // 线条样式
ctx.stroke();

在上述示例中,lineCap 属性的含义是在线条的两侧额外绘制图形、取值分别为 butt(默认)、roundsquare

如果使用 canvas 绘制若干线条,可以再次调用 moveTo、lineTo 以及 stroke 方法即可,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(50, 50);
ctx.lineTo(80, 50);
ctx.stroke();

ctx.moveTo(50, 100);
ctx.lineTo(80, 100);
ctx.stroke();

绘制线条时,如果自定义了线条宽度、颜色或样式,那么将影响之后的所有线条,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");
ctx.moveTo(50, 50);
ctx.lineTo(80, 50);
ctx.lineWidth = 10;
ctx.strokeStyle = "orangered";
ctx.lineCap = "round";
ctx.stroke();

ctx.moveTo(50, 100);
ctx.lineTo(80, 100);
ctx.stroke();

如果想要避免此类情况,那么必须:

  1. 通过绘图工具调用 beginPath 方法,以开启一个新路径
  2. 调用 stroke 方法之前重新设置线条样式,否则依旧相互影响

示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(50, 50);
ctx.lineTo(80, 50);
ctx.lineWidth = 10;
ctx.strokeStyle = "orangered";
ctx.lineCap = "round";
ctx.stroke();

ctx.beginPath();    // 开启一个新路径
ctx.moveTo(50, 100);
ctx.lineTo(80, 100);
ctx.lineWidth = 5;
ctx.strokeStyle = "yellowgreen";
ctx.stroke();

绘制图形

以下示例尝试绘制一个矩形:

<canvas width="500" height="500"></canvas>
<script>
    let oCanvas = document.querySelector("canvas");
    let ctx = oCanvas.getContext("2d");

    ctx.moveTo(100, 50);
    ctx.lineTo(200, 50);
    ctx.lineTo(200, 100);
    ctx.lineTo(100, 100);
    ctx.stroke();
</script>

默认情况下,不能将矩形闭合,此时可以通过绘图工具调用 closePath 方法,示例如下:、

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 100);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();

此外,canvas 可以设置各个点之间相互相连时的样式,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 100);
ctx.lineTo(100, 100);
ctx.lineWidth = 10;
ctx.lineJoin = "round";
ctx.closePath();
ctx.stroke();

lineJoin 属性的取值分别为 miter(默认)、round(圆角) 以及 bevel(切线)

填充图形

canvas 中可以使用 stroke 方法将所有的点相连起来,此外,也可以使用 fill 方法将一个图形以某种颜色填充,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(100, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 300);
ctx.lineTo(100, 300);
ctx.closePath();
ctx.fillStyle = "orange";
ctx.fill();

类似于 strokeStyle 属性,fillStyle 属性可以指定以何种颜色填充,通过绘图工具调用 fill 方法可以填充某个封闭图形

如果被填充的图形存在嵌套关系,那么图形是否填充由非零环绕原则决定:从被填充图形中的任意一点开始拟一条直线直至所有嵌套图形的最外层,此条直线将与所有嵌套图形的某一边相交,系统初始化一个检测值为零、之后检测每一个相交点所在的图形是以顺时针还是以逆时针相连,如果是顺时针,那么检测值加一,如果是逆时针,那么检测值减一,最后检查计算结果是否为零,如果非零,那么填充,如果为零,那么不填充,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

/* 顺时针 */
ctx.moveTo(100, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 300);
ctx.lineTo(100, 300);
ctx.closePath();
/* 顺时针 */
ctx.moveTo(150, 150);
ctx.lineTo(250, 150);
ctx.lineTo(250, 250);
ctx.lineTo(150, 250);
ctx.closePath();

ctx.stroke();

在上述示例中绘制图形时,从未开启一个新路径,而是使用同一个(默认)路径,此时并不用调用两次 stroke 方法,在最后调用一次即可,此外,如果没有开启一个新路径,那么所有使用的都是默认路径,在同一个路径下的所有样式是相同的,而非零环绕原则的必须在同一个路径下才有效,以下是在上述示例的基础上的填充示例:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

/* 顺时针 */
ctx.moveTo(100, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 300);
ctx.lineTo(100, 300);
ctx.closePath();
/* 顺时针 */
ctx.moveTo(150, 150);
ctx.lineTo(250, 150);
ctx.lineTo(250, 250);
ctx.lineTo(150, 250);
ctx.closePath();

ctx.fillStyle = "orange";
ctx.fill();

在上述示例中,内、外层图形均以顺时针相连,根据非零环绕原则,所以均被填充,以下是一个相反的例子:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

/* 顺时针 */
ctx.moveTo(100, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 300);
ctx.lineTo(100, 300);
ctx.closePath();
/* 逆时针 */
ctx.moveTo(150, 150);
ctx.lineTo(150, 250);
ctx.lineTo(250, 250);
ctx.lineTo(250, 150);
ctx.closePath();

ctx.fillStyle = "orange";
ctx.fill();

虚线

canvas 中可以绘制虚线,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineWidth = 10;
ctx.strokeStyle = "orange";
ctx.setLineDash([5, 10]);
ctx.stroke();

在上述示例中,通过绘图工具调用 setLineDash 方法,向方法传入一个数组,将实线绘制为虚线,每一个颜色部分的宽度为 5、空白间隙的宽度为 10,依次类推,实际上,数组中可以有任意多个值,颜色部分和空白间隙的宽度依次为数组中的值,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineWidth = 10;
ctx.strokeStyle = "orange";
ctx.setLineDash([5, 2, 1]);
ctx.stroke();

此外可以通过绘图工具调用 getLineDash 方法,此方法将虚线中最小不重复的一部分中所有颜色部分和空白间隙的宽度以数组的形式返回,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(100, 50);
ctx.lineTo(200, 50);
ctx.lineWidth = 10;
ctx.strokeStyle = "orange";
ctx.setLineDash([5, 2, 1]);
ctx.stroke();
console.log(ctx.getLineDash()); // [5, 2, 1, 5, 2, 1]

绘制矩形

在 canvas 中有多种绘制矩形的方式,最朴素的绘制方式如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(300, 100);
ctx.lineTo(300, 300);
ctx.lineTo(100, 300);
ctx.closePath();
ctx.stroke();

此外,可以通过绘图工具调用 rect 方法绘制矩形,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.rect(100, 100, 200, 200);
ctx.stroke();

也可以通过绘图工具调用 strokeRect 方法绘制矩形,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.strokeRect(100, 100, 200, 200);

相应的方法也可以使用 fill,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.fillRect(100, 100, 200, 200);
ctx.fillStyle = "yellow";
ctx.fillRect(150, 150, 100, 100);

在上述所有绘制矩形的方法中,参数分别为矩形左上顶点的坐标、矩形的宽度以及高度,此外,在 canvas 中可以通过绘图工具调用 clearRect 方法清除某一个矩形区域中的内容,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.fillRect(100, 100, 200, 200);
ctx.clearRect(0, 0, 200, 200);

渐变色

在 canvas 中填充图形时,可以以渐变色填充,步骤如下:

  1. 创建渐变方案
  2. 用渐变方案指定颜色渐变范围
  3. 以渐变方案填充图形

示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

/* 1. 创建渐变方案 */
let lg = ctx.createLinearGradient(100, 100, 300, 100);

/* 2. 用渐变方案指定渐变颜色范围 */
lg.addColorStop(0, "red");
lg.addColorStop(0.5, "green");
lg.addColorStop(1, "blue");

/* 3. 以渐变方案填充图形 */
ctx.fillStyle = lg;
ctx.fillRect(100, 100, 200, 200);

在上述示例中,通过绘图工具调用 createLinearGradient 方法创建线性渐变方案,参数表示坐标 (X0, Y0)、(X1, Y1) ,通过坐标可以计算出渐变方向与渐变范围,之后通过渐变方案调用 addColorStop 方法指定渐变颜色范围,在某个百分比中以某个颜色填充,以下示例演示径向渐变:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

/* 1. 创建绘图方案 */
let lg = ctx.createRadialGradient(100, 100, 150, 300, 300, 150);

/* 2. 用绘图方案指定渐变颜色范围 */
lg.addColorStop(0, "red");
lg.addColorStop(0.5, "green");
lg.addColorStop(1, "blue");

/* 3. 以绘图方案填充图形 */
ctx.fillStyle = lg;
ctx.fillRect(100, 100, 200, 200);

使用 createRadialGradient 方法创建径向渐变方案时,在每一个坐标之后必须指定径向半径

绘制圆弧

在 canvas 可以通过绘图工具调用 arc 方法绘制圆弧,示例如下;

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.arc(150, 150, 100, 0, Math.PI, true);
ctx.stroke();

上述示例中 arc 的函数原型如下:

arc(x, y, r, startAngle, endAngle, [dir]);

x, y 表示圆心坐标,r 表示半径、startAngle 和 endAngle 表示开始弧度和结束弧度,可选参数 dir 是一个 bool 值,表示以顺时针还是以逆时针绘制,默认情况下为 false,以顺时针绘制,以下是一个绘制扇形的示例:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.moveTo(150, 150);
ctx.arc(150, 150, 100, 0, Math.PI / 2);
ctx.closePath();
ctx.stroke();

绘制文字

在 canvas 中可以使用 Text 相关的方法绘制文字,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let canvasWidth = ctx.canvas.width;
let canvasHeight = ctx.canvas.height;

ctx.moveTo(0, canvasHeight / 2 - 0.5);
ctx.lineTo(canvasWidth, canvasHeight / 2 - 0.5);
ctx.stroke();
ctx.moveTo(canvasWidth / 2 - 0.5, 0);
ctx.lineTo(canvasWidth / 2 - 0.5, canvasHeight);
ctx.stroke();

let text = "Reyn Morales";
ctx.strokeText(text, canvasWidth / 2, canvasHeight / 2);

在上述示例中,首先绘制参考线,之后通过绘图工具调用 strokeText 方法将文字绘制于 canvas 中心,必须注意的是,通常图形的参考点为左上角,而文字的参考点为左下角,此外,也可以调用 fillText 方法绘制文字,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let canvasWidth = ctx.canvas.width;
let canvasHeight = ctx.canvas.height;

ctx.moveTo(0, canvasHeight / 2 - 0.5);
ctx.lineTo(canvasWidth, canvasHeight / 2 - 0.5);
ctx.stroke();
ctx.moveTo(canvasWidth / 2 - 0.5, 0);
ctx.lineTo(canvasWidth / 2 - 0.5, canvasHeight);
ctx.stroke();

let text = "Reyn Morales";
ctx.font = "30px 微软雅黑";
ctx.fillText(text, canvasWidth / 2, canvasHeight / 2);

在上述示例中,为了更好的演示效果,通过绘图工具调用 font 属性设置文本大小和字体

此外,在 canvas 中,可以通过 textAlign 和 textBaseline 属性分别设置文本的水平和垂直对齐方式,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let canvasWidth = ctx.canvas.width;
let canvasHeight = ctx.canvas.height;

ctx.moveTo(0, canvasHeight / 2 - 0.5);
ctx.lineTo(canvasWidth, canvasHeight / 2 - 0.5);
ctx.stroke();
ctx.moveTo(canvasWidth / 2 - 0.5, 0);
ctx.lineTo(canvasWidth / 2 - 0.5, canvasHeight);
ctx.stroke();

let text = "Reyn Morales";
ctx.font = "30px 微软雅黑";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.strokeText(text, canvasWidth / 2, canvasHeight / 2);

不论是水平对齐还是垂直对齐,总是以文本的左下角为参考点

绘制图片

在 canvas 中,可以通过绘图工具调用 drawImage 方法绘制图片,函数原型如下:

drawImage(imgDOM, [cropX], [cropY], [cropWidth], [cropHeight], startX, startY, [imgWidth], [imgHeight]);

原型中 crop 相关的参数表示从原始图片中截取指定宽高的图片,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let oImg = new Image();
oImg.onload = function () {
    ctx.drawImage(oImg, 50, 50, 200, 300);
}
oImg.src = "./images/SpiderMan.jpg";

transform 相关属性

在 canvas 中可以通过 translate、rotate 和 scale 属性调整坐标系的位置、角度以及缩放,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

ctx.translate(100, 100);
ctx.rotate(Math.PI / 4);
ctx.scale(0.5, 1);
ctx.strokeRect(100, 100, 200, 100);

必须注意的是,transform 相关的属性调整的是坐标系,不是图形

事件监听

示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let rectX = 100;
let rectY = 100;
let rectWidth = 200;
let rectHeight = 50;

ctx.rect(rectX, rectY, rectWidth, rectHeight);
ctx.fill();

oCanvas.onclick = function (event) {
    let locX = event.offsetX;
    let locY = event.offsetY;

    if (locX > rectX && locX < rectX + rectWidth && locY > rectY && locY < rectY + rectHeight) {
        console.log("Rect Click!");
    } else {
        console.log("No Rect Click!");
    }
}

在上述示例中,通过判断鼠标点击时的位置是否在矩形中来判断矩形的点击事件,canvas 中提供了一个方法专门用于判断某个坐标是否在图形中,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let rectX = 100;
let rectY = 100;
let rectWidth = 200;
let rectHeight = 50;

ctx.rect(rectX, rectY, rectWidth, rectHeight);
ctx.fill();

oCanvas.onclick = function (event) {
    let locX = event.offsetX;
    let locY = event.offsetY;

    console.log(ctx.isPointInPath(locX, locY));
}

isPointInPath 方法可以判断传入的坐标是否在绘制图形的路径中,如果在,则返回 true,否则返回 false,亦即,在同一个路径下的所有图形都可以被判断在图形中,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let rectX = 100;
let rectY = 100;
let rectWidth = 200;
let rectHeight = 50;

ctx.rect(rectX, rectY, rectWidth, rectHeight);
ctx.fill();

ctx.rect(200, 200, 100, 100);
ctx.fill();

oCanvas.onclick = function (event) {
    let locX = event.offsetX;
    let locY = event.offsetY;

    console.log(ctx.isPointInPath(locX, locY));
}

isPointInPath 方法通常判断最新路径,示例如下:

let oCanvas = document.querySelector("canvas");
let ctx = oCanvas.getContext("2d");

let rectX = 100;
let rectY = 100;
let rectWidth = 200;
let rectHeight = 50;

ctx.rect(rectX, rectY, rectWidth, rectHeight);
ctx.fill();

ctx.beginPath();
ctx.rect(200, 200, 100, 100);
ctx.fill();

oCanvas.onclick = function (event) {
    let locX = event.offsetX;
    let locY = event.offsetY;

    console.log(ctx.isPointInPath(locX, locY));
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值