3

Canvas简单入门

 1 year ago
source link: https://www.clzczh.top/2022/05/29/Canvas/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Canvas简单入门

创建canvas至少需要提供widthheight属性,才能通知浏览器需要多大位置画图。标签的内容是后备数据,在浏览器不支持canvas元素时显示。

<canvas id="mycanvas" width="200" height="200">haha</canvas>

可以通过if(canvas.getContext)来判断浏览器是否支持canvas

通过canvas.getContext('2d')可以获取 2D 绘图上下文。2D 绘图上下文提供了绘制 2D 图形的方法。左边原点(0, 0)在 canvas元素的左上角,x 坐标向右增长,y 坐标向下增长。

从画布上导出一张 PNG 格式的图片

<body>
  <canvas id="mycanvas" width="200" height="200">haha</canvas>

  <script>
    const mycanvas = document.getElementById("mycanvas");

    // 确保浏览器支持canvas
    if (mycanvas.getContext) {
      // 获得图像的数据URI
      const imgURI = mycanvas.toDataURL("image/png");
      console.log(imgURI);
    }
  </script>
</body>
image-20220522110709840
image-20220522110709840

我们查看控制台可以发现,输出了一串base64编码,也就是说,canvas.toDataURL就是将画布 canvas转换成base64编码。

填充与描边

  • 填充就是以特定的样式填充形状,包括颜色、渐变、图像

  • 描边就是只给形状边界着色。

显示效果取决于两个属性:fillStylestrokeStyle

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.fillStyle = "#000";
  context.strokeStyle = "red";
}

没有效果?
别急,这是因为我们只是设置了填充和描边而已,想要它生效,还需要绘制出来才能有效果。

与绘制矩形相关的方法有三个。它们都接收 4 个参数:矩形 x 坐标、矩形 y 坐标、矩形宽度和矩形高度。(单位是像素,但是传参时不需要传单位)

  1. fillRect
  2. strokeRect
  3. clearRect

fillRect:绘制并填充矩形

fillRect:以指定颜色在画布上绘制并填充矩形,填充色使用fillStyle来设置。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.fillStyle = "pink";
  context.fillRect(10, 10, 50, 50);

  context.fillStyle = "rgba(0, 0, 0, .1)";
  context.fillRect(30, 30, 50, 50);
}
image-20220522110314237
image-20220522110314237

stokeRect:绘制矩形轮廓

stokeRect:绘制矩形轮廓,颜色由strokeStyle来指定。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.strokeStyle = "red";
  // 设置描边宽度
  context.lineWidth = 5;
  context.strokeRect(10, 10, 50, 50);

  context.strokeStyle = "blue";
  context.fillStyle = "rgba(0, 0, 0, .1)";
  context.strokeRect(30, 30, 50, 50);
}
image-20220522110410841
image-20220522110410841

clearRect:擦除画布中某个区域

clearRect:擦除画布中某个区域,把擦除的区域变透明。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.fillStyle = "red";
  context.fillRect(0, 0, 200, 200);

  context.clearRect(50, 50, 100, 100);
}
image-20220522110449360
image-20220522110449360

绘制路径需要先调用beginPath,表示要开始绘制路径,再调用以下方法来绘制路径。

  • lineTo(x, y):绘制一条从上一个点到(x, y)的直线
  • moveTo(x, y):不绘制线条,只是把画笔移动到(x, y)
  • 更多

绘制完路径后,可以指定fillStyle属性并调用fill方法来填充路径,也可以指定strokeStyle属性并调用stoke方法来描画路径。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  // 创建路径
  context.beginPath();

  // 绘制圆弧,参数分别是圆心x坐标、圆形y坐标、圆弧半径、圆弧起始点(单位:弧度)、圆弧终点(单位:弧度)、绘制方向(false为顺时针绘制,true为逆时针绘制)
  context.arc(100, 100, 99, 0, 2 * Math.PI, true);

  // context.fillStyle = 'pink'
  // context.fill()

  context.strokeStyle = "pink";
  context.stroke();
}
image-20220522111214015
image-20220522111214015

还可以调用clip方法创建一个新的剪切区域。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  // 创建路径
  context.beginPath();

  // 绘制圆弧,参数分别是圆心x坐标、圆形y坐标、圆弧半径、圆弧起始点(单位:弧度)、圆弧终点(单位:弧度)、绘制方向(false为顺时针绘制,true为逆时针绘制)
  context.arc(100, 100, 50, 0, 2 * Math.PI, true);

  context.fillStyle = "pink";
  context.clip();

  context.fillRect(0, 0, 100, 100);
}

上面的扇形怎么出来的呢?
我们可以把clip变成fill,看下没有被剪切的话,是什么样子。

img
img

也就是说,实际上剪切就是两个图形相交部分。

如果使用lineTo需要注意:没有设置moveTo时,这个位置并不是(0, 0),而是空,所以第一次的lineTo没法画出结果。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");
  // 创建路径
  context.beginPath();

  // context.moveTo(0, 0);
  context.lineTo(100, 50);
  context.lineTo(200, 0);

  context.lineWidth = 8;
  context.strokeStyle = "pink";

  // 描画路径
  context.stroke();
}

没有moveTo

image-20220522112404156
image-20220522112404156

moveTo

image-20220522112440786
image-20220522112440786

beginPath 的作用

上面的例子中,beginPath并没有作用,也就是说上面的例子中,其实有没有beginPath都一样。那么beginPath有什么作用呢?

beginPath表示下面绘制的图形是一个新的路径。具体看下实例。

const context = mycanvas.getContext("2d");
// 创建路径
context.beginPath();

context.moveTo(0, 0);
context.lineTo(100, 50);

context.lineWidth = 8;
context.strokeStyle = "pink";

// 描画路径
context.stroke();

context.lineTo(200, 0);
context.strokeStyle = "purple";
context.stroke();
image-20220522112513152
image-20220522112513152

想要的效果是画出两条不一样颜色的线,但是最后是一种颜色折线,这是因为我们只是用了一次beginPath,所以就会把这两条线当成同一个路径,最后调用的stroke就会把原本是粉色的线再用紫色画一遍,所以最终的效果就是只有一条折线。

所以需要使用beginPath创建新路径,新的路径还是会有没有设置moveTo时,这个位置并不是(0, 0),而是空的问题,所以需要使用moveTo设置位置

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");
  // 创建路径
  context.beginPath();

  context.moveTo(0, 0);
  context.lineTo(100, 50);

  context.lineWidth = 8;
  context.strokeStyle = "pink";
  context.stroke();

  context.beginPath();
  // 创建新的路径,需要重新设置位置
  context.moveTo(100, 50);
  context.lineTo(200, 0);
  context.strokeStyle = "purple";
  context.stroke();
}
image-20220522112538976
image-20220522112538976

closePath 的作用

有可能会陷进closePath是结束路径的误区,认为closePath就是beginPath的配套。但是closePathbeginPath并不是配套的,它们的功能不一样。所以closePath之后的路径也不是新的路径,只有beginPath才行。

closePath的作用是将最近绘制的路径闭合,和之前有没有beginPath无关

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  // context.beginPath();  // 有无`beginPath`都没有影响

  context.moveTo(10, 10);
  context.lineTo(100, 50);
  context.lineTo(20, 70);
  context.closePath();

  context.lineWidth = 8;
  context.strokeStyle = "pink";
  context.stroke();
}
image-20220522112629070
image-20220522112629070

上面我们只绘制了两条线,但是最终得到的结果是一个三角形,这是因为我们使用closePath把最近绘制的路径闭合了。

绘制文本有两种方法。

  1. fillText:使用fillStyle属性绘制文本
  2. strokeText:使用strokeStyle属性绘制文本
const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.moveTo(10, 10);
  context.lineTo(150, 75);
  context.lineTo(30, 100);
  context.closePath();

  context.lineWidth = 1;
  context.strokeStyle = "pink";

  context.fillStyle = "purple";
  context.fillText("CLZ", 50, 60);
  context.strokeText("CLZ", 50, 80);

  context.stroke();
}
image-20220522112741066
image-20220522112741066

可以通过fonttextAligntextBaseline属性设置文本的字体、对齐方式、基线。

context.font = "700 16px Arial";
img
img

textAlign

  • 如果是start,那么 x 坐标就是文本的左侧坐标
  • 如果是center,那么 x 坐标就是文本的中心点坐标
  • 如果是end,那么 x 坐标就是文本的右侧坐标
const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.moveTo(10, 10);
  context.lineTo(150, 75);
  context.lineTo(30, 100);
  context.closePath();

  context.lineWidth = 1;
  context.strokeStyle = "pink";

  context.font = "700 16px Arial";
  context.fillStyle = "purple";

  context.textAlign = "start";
  context.strokeText("CLZ", 50, 50);

  context.textAlign = "center";
  context.fillText("CLZ", 50, 65);

  context.textAlign = "end";
  context.strokeText("CLZ", 50, 80);

  context.stroke();
}
image-20220522112924112
image-20220522112924112

textBaseline类似

2D 换图上下文支持所有常见的绘制变化。
rotate(a):围绕原点把图像旋转 a 弧度
scale(x, y):缩放图像
translate(x, y):移动原点

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  // 创建路径
  context.beginPath();

  // 绘制圆弧,参数分别是圆心x坐标、圆形y坐标、圆弧半径、圆弧起始点(单位:弧度)、圆弧终点(单位:弧度)、绘制方向(false为顺时针绘制,true为逆时针绘制)
  context.arc(100, 100, 50, 0, 2 * Math.PI, true);

  context.lineWidth = "8";
  context.strokeStyle = "pink";

  // 移动原点
  context.translate(100, 100);

  // 旋转
  context.rotate(Math.PI);

  // 缩放
  context.scale(0.75, 0.75);

  // 因为已经移动过原点了,所以这时候(0, 0)就是圆心
  context.moveTo(0, 0);
  context.lineTo(25, 30);

  context.stroke();
}
image-20220522113048545
image-20220522113048545

上面的例子中,已经把很多变化都使用上了,如果想要了解具体例子可以注释掉其他部分。

save 和 restore 的作用

save方法可以保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。后续可以通过restore方法,恢复上下文的设置和变换。saverestore的使用类似于栈,后进先出。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.fillStyle = "red";
  context.save();

  context.fillStyle = "blue";
  context.translate(100, 100);
  context.save();

  context.fillStyle = "purple";
  context.translate(-100, -100);
  context.fillRect(0, 0, 100, 100);

  context.restore();
  context.fillRect(0, 0, 100, 100);

  context.restore();
  context.fillRect(100, 0, 100, 100);

  context.restore();
  context.fillRect(0, 100, 100, 100);
}
image-20220522113308234
image-20220522113308234

分析:设 XXX 为绘图上下文的设置和变化

  1. 设置填充色为红色,save保存
  2. 设置填充色为蓝色,移动原点,save保存
  3. 设置填充色为紫色,移动原点,画出紫色的矩形
  4. restore恢复XXX,此时,原点为(100, 100),填充色为蓝色。画出蓝色的矩形
  5. restore恢复**XXX**,此时,原点为(0, 0),填充色为红色。画出红色的矩形
  6. restore已经没有保存的XXX,所以XXX不会变化
<img src="./avatar.png" alt="">
<canvas id="mycanvas" width="200" height="200">haha</canvas>

通过drawImage把 HTML 的 img 元素或另一个 canvas 元素绘制到当前画布中。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  // 获取图像
  const img = document.images[0];

  // 在画布的坐标出绘制图像,此时图像和原来的图像一样大,指的是原文件的大小
  // context.drawImage(img, 10, 10)

  // 传入另外两个参数,设置绘制图像的宽高
  context.drawImage(img, 10, 10, 100, 100);
}

只传3个参数,画到画布上的跟原来的图像一样大,但画布没那么大。所以会只有一部分。

image-20220522113739829
image-20220522113739829

传入五个参数,可以让设置图像的宽高,显示完整的图像。

image-20220522113901338
image-20220522113901338

去掉DOM树上的img

上面的做法是需要html中有img元素才能执行的.实际上,我们也可以通过image对象来实现。

即获取图像不再是通过document.images[0],而是

const img = new Image();
img.src = "./avatar.png";

另外,绘制图像应该在imgload事件回调中调用。

const img = new Image();
img.src = "./avatar.png";
img.onload = () => {
  // 传入另外两个参数,设置绘制图像的宽高
  context.drawImage(img, 10, 10, 100, 100);
};
image-20220522114113263
image-20220522114113263

还可以接收 9 个参数,实现把原始图像的一部分绘制到画布上。
如:context.drawImage(img, 0, 10, 50, 50, 0, 100, 20, 30),从原始图像的(0, 10)开始,50 像素宽、50 像素高,画到画布上(0, 100)开始,宽 40 像素、高 60 像素。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  // 获取图像
  const img = document.images[0];

  // // 9个参数
  context.drawImage(img, 0, 10, 300, 300, 100, 100, 40, 40);
}
image-20220522114232024
image-20220522114232024

操作的结果可以使用canvas.toDataURL()方法获取。

再搭配下载图片的方式就能实现下载图片。(这里用的是a标签方法)

const a = document.createElement("a");
a.href = mycanvas.toDataURL();

// 获取源图片的名字
a.download = img.src.split("/")[img.src.split("/").length - 1];

a.click();

设置好阴影有关的属性值,就能够自动为要绘制的形状或路径生成阴影

  • shadowOffsetX:阴影相对于形状或路径的 x 坐标偏移。默认为 0
  • shadowOffsetY:阴影相对于形状或路径的 y 坐标偏移。默认为 0
  • shadowBlur:阴影的模糊量。默认值为 0,表示不模糊
  • shadowColor:阴影的颜色。默认为黑色
const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  context.shadowOffsetX = 5;
  context.shadowOffsetY = 10;
  context.shadowBlur = 5;
  context.shadowColor = "rgba(0, 0, 0, .2)";

  context.fillStyle = "red";
  context.fillRect(0, 0, 50, 50);

  context.moveTo(100, 100);
  context.lineTo(180, 20);

  context.lineWidth = 12;
  context.stroke();
}
image-20220522114457032
image-20220522114457032

线性渐变可以调用上下文的createLinearGradient方法,接收四个参数:起点 x 坐标、起点 y 坐标、终点 x 坐标、终点 y 坐标,创建CanvasGradient对象。

有了渐变对象后,就需要添加渐变色标了,通过addColorStop可以添加色标,第一个参数范围为 0~1,第二个参数是 CSS 颜色字符串。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  const gradient = context.createLinearGradient(10, 10, 180, 180);

  gradient.addColorStop(0, "red");
  gradient.addColorStop(0.5, "blue");
  gradient.addColorStop(1, "purple");

  context.fillStyle = gradient;
  context.fillRect(0, 0, 200, 200);
}
image-20220522114554072
image-20220522114554072

为了让渐变覆盖整个矩形,渐变的坐标和矩形的坐标应该搭配合适,不然只会显示部分渐变。

还可以调用上下文的createRadialGradient方法来创建径向渐变。接收 6 个参数,前 3 个参数指定起点圆形中心的 x 坐标、y 坐标和半径,后 3 个参数指定终点圆形中心的 x 坐标和半径。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  let gradient = context.createRadialGradient(100, 100, 20, 100, 100, 80);
  gradient.addColorStop(0, "white");
  gradient.addColorStop(1, "black");

  context.fillStyle = gradient;
  context.fillRect(0, 0, 200, 200);
}

上面这个渐变,简单理解就是内层圆为半径为 20 像素的纯白圆,外层圆为 80 像素的白渐变黑圆,剩余部分就是黑色。

image-20220522114630535
image-20220522114630535

图案适用于填充和描画图形的重复图像。
通过createPattern方法,该方法接收两个参数,第一个参数是img元素,第二个参数是是否重复,和background-repeat属性一样。

然后,像渐变一样,把pattern对象赋值给fillStyle属性即可。

这个图案实际上就有点背景图像的味道了,通过创建pattern对象,来控制图像的重复。然后,给绘图上下文的fillStyle赋值,设置填充样式,最后再通过fillRect来设置图案的位置和大小。

const mycanvas = document.getElementById("mycanvas");

// 确保浏览器支持canvas
if (mycanvas.getContext) {
  const context = mycanvas.getContext("2d");

  const image = document.images[0];

  const pattern = context.createPattern(image, "repeat");
  // const pattern = context.createPattern(image, 'repeat-y')
  // const pattern = context.createPattern(image, 'no-repeat')

  context.fillStyle = pattern;
  context.fillRect(0, 0, 190, 190);
}
image-20220522115145807
image-20220522115145807

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK