EXIT

00:00:00

Canvas 从入门到入坟

[toc]


序章

# 创建一个canvas

<canvas id="canvas" ref="canvas" width="800" height="600"></canvas> ... <script vue> const canvas = ref<HTMLCanvasElement | null>(null); onMounted(()=>{ const ctx = canvas.value.getContext('2d'); }) </script>

canvas需要一个width height来确定canvas的大小.

canvas主要用于2d图形的绘制.而同属于canvas的webGL API则是用来3d图形的 绘制.

正常的canvas中间是不能添加文字的.而不支持canvas的浏览器,会将canvas当做平常的容器看待,所以会显示插入的文字.

可以通过 canvas.getContext(type:string,attributes:{}) API 来获得渲染上下文和绘画的功能.其中,

type为绘制类型,可选参数有:

  • '2d': 建立一个二维渲染上下文。 等同于 CanvasRenderingContext2D();
  • 'webgl': 创建一个 WebGLRenderingContext 三维渲染上下文对象。
  • 'webgl2': 创建一个 WebGL2RenderingContext 三维渲染上下文对象。
  • 'bitmaprenderer': 创建一个只提供将canvas内容替换为指定ImageBitmap功能的ImageBitmapRenderingContext。

2d类型时,attributes为绘制属性,可选参数有:

  • alpha:boolean => false 表示背景永远是不透明的,这样会有性能提升(不透明默认黑色).
  • willReadFrequently:boolean => 表示是否要重复进行操作.频繁调用能节省内存,但是仅Gecko内核浏览器支持.
  • storage : string ,表示存储类型,默认为persisten,表示持久化存储.

注意📢:在同一个Canvas上,使用相同的类型参数contextType多次调用getContext()方法只会返回同一个绘制上下文。

绘制图形

# 路径的开启与闭合

  • beginPath(): 用来开始一条新的路径.
  • closePath(): 用来关闭一条路径.

因此,在进行绘制时,beginPath和closePath要搭配使用.开始和结束代表一段完整的绘制.

# 绘制方式

绘制方式有两种:描边填充.

  • Stroke(): 描边.
  • Fill(): 填充.

# 样式设置

  • 描边的样式设置: ctx.strokeStyle
  • 填充的样式设置: ctx.fillStyle

注意:样式设置要在图形绘制之前.

注意:样式一次设置永久有效,要想改变,必须重新设置覆盖原有值.

# 绘制直线

绘制直线需要了解三个函数:

  • moveTo(x,y) : 绘制直线的起点(x,y);
  • lineTo(x,y) : 绘制直线的终点(x,y);
  • Stroke() : 开始绘制函数.通过线条来绘制图形轮廓.(描边)

其中,x,y坐标轴在页面上的起点为:

image-20231219200123264

值得注意的是,可以调用多个moveTo()和lineTo()函数对,最终执行Stroke()函数进行绘制.

当然,也可以确定起点之后,通过LineTo()不断地进行绘制.lineTo()每次进行绘制的时候都是上次绘制的终点作为起点.

(连续的直线只需要设置一个起始点,*就像一个真正的画笔一样.)

直线的样式设置(不如说是stroke的样式设置):

  • lineWidth: 设置直线的粗细,默认为1,只能为正数.
  • lineCap: 设置直线端点的样式,可选: buttroundsquare, 默认是 butt.
  • lineJoin: 设置两线段连接处(lineTo连续绘制时)的显示样式:可选值为:round, bevelmiter。默认是 miter。
  • miterLimit: 设置两条线段相交时(lineTo连续绘制时)交接处的最大长度(线段交接的点,内角顶点到外角顶点的长度.)
  • setLineDash: 设置虚线样式[数组] 注意:设置的虚线样式会是偶数长度数组(即使你设置的是奇数个,他也会进行拓展到偶数个长度). 其中虚线数组的含义是:[实线长度,空白长度,实现长度,空白长度...];
  • getLineDash: 获取设置的虚线样式[偶数长度的数组].
  • LineDashOffset: 设置虚线样式的起始偏移.

# 绘制矩形

绘制矩形的函数有:

  • rect(x,y,width,height): 绘制出一个矩形,但是后续需要用stroke或者fill方法进行渲染.
  • strokeRect(x,y,width,height) :绘制并渲染出一个矩形边框.
  • fillRect(x,y,width,height): 绘制并渲染一个矩形填充.
    • x,y:表示矩形的左上角的位置.
    • width,height:表示宽与高.

# 绘制圆 / 圆弧

绘制圆的函数为:

  • arc(x,y,radius,startAngle,endAngle,anticlockwise), 其中:
    • x,y:圆/圆弧的中心位置;
    • radius:圆/圆弧的半径;
    • startAngle:圆/圆弧的起始点,从x轴方向开始计算,且单位为弧度(从x轴的右侧开始算起[1,4]象限).
    • endAngle:为圆/圆弧的终点,单位也为弧度(!注意:表示的是终点的弧度位置,并不表示绘制了多少弧度).
    • anticlockwise:可选,boolean类型,表示圆/圆弧的绘制方向,默认为false,表示顺时针绘制圆/圆弧.

其中弧度的计算公式为: 弧度 = 角度 * Math.PI / 180;

# 绘制椭圆 / 椭圆弧

绘制椭圆的函数为:

  • ellipse(x,y,radiusX,radiusY,rotation,startAngle,endAngle,anticlockwise),其中:
    • x,y:表示椭圆的中心位置;
    • radiusX,radiusY:表示椭圆的X轴上的半径和Y轴上的半径;
    • rotation:表示椭圆的旋转角度,以弧度表示;
    • startAngle,endAngle,anticlockwise:...;

# 二次贝塞尔曲线

绘制二次贝塞尔曲线的函数为:

  • quadraticCurveTo(cp1x,cp1y,x,y),其中:
    • cp1x,cp1y为控制点坐标;
    • x,y为结束点坐标;

quadraticCurveTo需要搭配moveTo函数进行使用.

# 三次贝塞尔曲线

绘制三次贝塞尔曲线的函数为:

  • bezierCurveTo(cp1x,cp1y, cp2x,cp2y, x, y),其中:
    • cp1x,cp1y为第一个控制点坐标;
    • cp2x,cp2y为第二个控制点坐标;
    • x,y为结束点坐标;

bezierCurveTo需要搭配moveTo函数进行使用.

# N阶贝塞尔曲线

canvas还可以绘制四次 五次...贝塞尔曲线.涉及到贝塞尔曲线算法.

图形的样式

# 透明度

透明度是通过设置 globalAlpha 属性或者使用有透明度的样式作为轮廓或填充来实现。

  • ctx.globalAlpha = 0.5;
  • ctx.fillStyle = "rgba(0, 255, 0, 0.2)";
  • ctx.strokeStyle = "rgba(255, 0, 0, 0.7)";

# 渐变

-- ## 线性渐变

线性渐变顾名思义就是以一个点开始沿某个方向的渐变.

  • 创建一个线性渐变:const gradient = ctx.createLinearGradient(x1,y1,x2,y2):

    • x1,y1为起点坐标,

    • x2,y2为终点坐标.

  • 创建完线性渐变后,还需要一个方法来添加线性渐变:gradient.addColorStop(offset,color):

    • offset 表示颜色偏移,只能取0-1,表示从何处开始渐变.

      例如: gradient.addColorStop(0,"red"):表示是红色开始.

      //如果没有上面一段代码,下面的代码则表示从开始到50%的长度,都是蓝色.

      gradient.addColorStop(0.5,"blue"):表示从开始到50%的长度,由红色逐渐渐变到蓝色.

      gradient.addColorStop(1,"black"):表示从50%到结束,由蓝色逐渐渐变到黑色.

    • color表示颜色.

  • 最终再由strokeStyle/fillStyle = gradient 进行设置即完成了线性渐变.

-- ## 径向渐变

径向渐变是从起点到终点颜色从内到外进行圆形的渐变.

  • 创建一个径向渐变:const gradient = ctx.createRadialGradient(x0,y0,r0,x1,y1,r1):
    • x0, y0为开始圆的坐标
    • r0为开始圆的半径
    • x1, y1为结束圆的坐标
    • r1为结束圆的半径
  • 剩余步骤和线性渐变一样.

==疑问?== > 径向渐变的大小会影响原图形的大小?

# 图案样式

图案样式的意思就是将一个图像绘制到canvas中. 需要用 ctx.createPattern 方法来实现。

  • const ptrn = createPattern(image, type):

    • image:一个image对象,也可以是一个canvas对象.

    • type:绘制的种类:repeat,repeat-x,repeat-y 和 no-repeat。

  • 然后再将样式设置为ptrn即可完成图案样式: ctx.fillStyle/strokeStyle = ptrn;

绘制文本

绘制文本也有绘制轮廓和填充两种.

  • ctx.strokeText(txt,x,y,maxWidth): 以轮廓的方式进行绘制文本.
  • ctx.fillText(txt,x,y,maxWidth): 以填充的方式进行文本绘制.
    • txt:内容.
    • x,y:绘制文本的起始位置.
    • maxWidth:可选参数,文本绘制的最大宽度,将会将文本缩小到当前maxwidth之内.

# 文本样式

  • 用来设置文字的字号和字体: ctx.font = "20px Arial";

  • 用来设置文本的对齐方式: ctx.textAlign = "left/right/center/start/end";

    从对齐方式(ctx.textAlign)上来说,left和start right和end是一样的.

    left/start 表示 文本的第一个字到设置的绘制文本x轴开始.

    right/end 表示 文本的最后一个字到设置的绘制文本x轴结束.

    center 表示 文本的中间的字位于设置的绘制文本x轴位置.

  • 用来设置文本的方向: ctx.direction = "inherit / ltr /rtl";

    inherit:继承

    Ltr:文字从左向右 : 正常

    Rtl:文字从右向左 : 会起到和textAlign right/end一样的作用.当设置为rtl时,ctx.textAlign的left和end相同,right和start相同.

  • 设置文字的垂直对齐方式:ctx.textBaseline = top hanging middle alphabetic ideographic bottom

image-20231220180655308
  • 设置文字的阴影:
    • ctx.shadowOffsetX:设置阴影在 X轴 上的延伸距离 + 右 - 左
    • ctx.shadowOffsetY:设置阴影在 Y轴 上的延伸距离 + 下 - 上
    • ctx.shadowBlur:用于设置阴影的模糊程度.
    • ctx.shadowColor:用于设置阴影的颜色.
  • 获得字体的各种属性 measureText:
    • ctx.measureText(txt).width:获得txt所占的宽度.

绘制图像

绘制图像主要用到的函数为:

  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

    满写的情况是需要对图像进行裁剪的,sx,sy:开始裁剪的位置(左上角),sWidth,sHeight裁剪的大小(像素).

  • 函数存在重载:

  • drawImage(image, dx, dy);

    这种情况下,图像并不会缩放,而是将图像满足canvas大小的像素点铺满canvas.

  • drawImage(image, dx, dy,dWidth, dHeight);

    这种情况下,图像会缩放,将图像完整的显示出来,并占满整个dWidth和dHeight.

参数:

  • Image:绘制的图像.
  • sx,sy:裁剪框左上角的位置坐标.
  • sWidth,sHeight:裁剪框的宽与高.
  • dx,dy:绘制元素时左上角的位置坐标.
  • dWidth,dHeight:绘制元素的宽与高(px) (如果不设置,则在绘制时image宽度和高度不会缩放).

动画

# 几何变换

--画布移动

ctx.translate(x, y): x,y表示偏移量.注意:将画布原心移动x,y偏移量的位置

--画布旋转

ctx.rotate(angle): angle表示顺时针旋转的弧度.注意:旋转的原点是相对于canvas的(0,0)坐标点,而不是相对于元素

如果ctx.translate(x, y) 过了,则旋转的原点则是相对于移动过的x,y

--画布缩放

scale(x,y):其中x为水平缩放的值,y为垂直缩放得值.注意:缩放也是相对于canvas原点.

==!注意== 以上任何操作完毕之后的画布都将成为下一次操作的新画布.

例如:在画布上进行了旋转rotate,那么在下一次进行移动/缩放/旋转时将依据已经旋转了的画布

如果不想依据旋转了的画布,可以通过ctx.save() 和 ctx.restore() 存取画布.

--画布变换

transform(a, b, c, d, e, f):

  • a:水平缩放,不缩放为1

  • b:水平倾斜,不倾斜为0

  • c:垂直倾斜,不倾斜为0

  • d:垂直缩放,不缩放为1

  • e:水平移动,不移动为0

  • f:垂直移动,不移动为0

  • 实现旋转的方法:

  • const deg = Math.PI / 180; const c3 = Math.cos(旋转角度 * deg); const s3 = Math.sin(旋转角度 * deg); ctx.transform(c3, s3, -s3, c3, 0, 0);

# 状态的保存与恢复

什么是状态的保存和恢复呢?

当我们在进行canvas绘制的时候,其实每次绘制完都是canvas的一个快照,而每个快照的状态是可以保存起来的.当我们再次需要使用到这个快照的时候,可以将这个快照进行恢复.

状态的保存和恢复 用到的方法是 ctx.save()ctx.restore().方法不需要参数.

save() restore() 贯穿整个画布的始终.

绘画的状态(可以保存和恢复的状态)有:

应用的变形:移动、旋转、缩放、strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterLimit、lineDashOffset、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、globalCompositeOperation、font、textAlign、textBaseline、direction、imageSmoothingEnabled等。

应用的裁切路径(clipping path)

# 多个图像的叠加状态

ctx.globalCompositeOperation 用来控制多个图像的叠加状态:

{ "source-over": "这是默认设置,并在现有画布上下文之上绘制新图形。" "source-in": "新图形只在新图形和目标画布重叠的地方绘制。其他的都是透明的。" "source-out": "在不与现有画布内容重叠的地方绘制新图形。" "source-atop": "新图形只在与现有画布内容重叠的地方绘制。" "destination-over": "在现有的画布内容后面绘制新的图形。" "destination-in": "现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。" "destination-out": "现有内容保持在新图形不重叠的地方。" "destination-atop": "现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。" "lighter": "两个重叠图形的颜色是通过颜色值相加来确定的。" "copy": "只显示新图形。" "xor": "图像中,那些重叠和正常绘制之外的其他地方是透明的。" "multiply": "将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。" "screen": "像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。" "overlay": "multiply 和 screen 的结合,原本暗的地方更暗,原本亮的地方更亮。" "darken": "保留两个图层中最暗的像素。" "lighten": "保留两个图层中最亮的像素。" "color-dodge": "将底层除以顶层的反置。" "color-burn": "将反置的底层除以顶层,然后将结果反过来。" "hard-light": "multiply 和 screen 的结合,类似于叠加,但上下图层互换了。" "soft-light": "用顶层减去底层或者相反来得到一个正值。" "difference": "一个柔和版本的 hard-light。纯黑或纯白不会导致纯黑或纯白。" "exclusion": "和 difference 相似,但对比度较低。" "hue": "保留了底层的亮度和色度,同时采用了顶层的色调。" "saturation": "保留底层的亮度和色调,同时采用顶层的色度。" "color": "保留了底层的亮度,同时采用了顶层的色调和色度。" "luminosity": "保持底层的色调和色度,同时采用顶层的亮度。" }

# 画布清空

  • 语法:clearRect(x, y, width, height)

  • 参数:

    • x为要清除的矩形区域左上角的x坐标,如果原点移动过,则要根据移动过的x坐标
    • y为要清除的矩形区域左上角的y坐标
    • width为要清除的矩形区域的宽度
    • height为要清除的矩形区域的高度

# 动画的执行

动画执行有三种方式:

  • setInterval
  • setTimeout
  • requestAnimationFrame

canvas事件响应

canvas事件响应包括鼠标事件和键盘事件.

鼠标事件中canvas提供了一个函数:

ctx.isPointInPath(x,y):此方法可以将x,y位置传入,然后判断是否在路径之内.

ctx.isPointInPath()仅支持绘制方法.stroke/fill ,不支持 drawImage

键盘事件能够响应的前提是 canvas 属于聚焦状态.

如何使canvas处于聚焦状态?

方法1:让canvas处于聚焦状态.

  • 首先需要为<canvas>标签添加tabindex="0"属性
  • 获取canvas元素以后,需要调用focus()方法让canvas自动获取焦点
  • 需要注意,当鼠标点击别的元素的时候,canvas元素会失去焦点,从而失去键盘事件

方法2:通过为windows对象添加键盘事件,从而控制canvas元素。

canvas图片保存

语法:

  • toDataURL(type, encoderOptions)

参数:

  • type:type为图片格式,默认为image/png,也可指定为:image/jpegimage/webp等格式
  • encoderOptions:encoderOptions为图片的质量,默认值 0.92。在指定图片格式为 image/jpegimage/webp 的情况下,可以从 01 的区间内选择图片的质量。如果不在这个范围内,则使用默认值 0.92

保存方法:

// 点击截图函数 function clickFn(){ // 将canvas转换成base64的url let url = canvas.toDataURL("image/png"); // 如果想直接展示在页面上,可以把Canvas 转化为图片,然后放入页面 let Img = document.getElementById('img'); Img.src = url; // 将base64转换为文件对象 let arr = url.split(",") let mime = arr[0].match(/:(.*?);/)[1] // 此处得到的为文件类型 let bstr = atob(arr[1]) // 此处将base64解码 let n = bstr.length let u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } // 通过以下方式将以上变量生成文件对象,三个参数分别为文件内容、文件名、文件类型 let file = new File([u8arr], "filename", { type: mime }); // 将文件对象通过a标签下载 let aDom = document.createElement("a"); // 创建一个 a 标签 aDom.download = file.name; // 设置文件名 let href = URL.createObjectURL(file); // 将file对象转成 UTF-16 字符串 aDom.href = href; // 放入href document.body.appendChild(aDom); // 将a标签插入 body aDom.click(); // 触发 a 标签的点击 document.body.removeChild(aDom); // 移除刚才插入的 a 标签 URL.revokeObjectURL(href); // 释放刚才生成的 UTF-16 字符串 };

canvas元数据

# getImageData

getImageData()方法可以返回一个ImageData对象。

ImageData对象用来描述canvas区域隐含的像素数据,此区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh.

语法:

  • ctx.getImageData(sx, sy, sw, sh)

参数:

  • sx:将要被提取的图像数据矩形区域的左上角 x 坐标。
  • sy:将要被提取的图像数据矩形区域的左上角 y 坐标。
  • sw:将要被提取的图像数据矩形区域的宽度。
  • sh:将要被提取的图像数据矩形区域的高度。

# putImageData

putImageData()方法和getImageData()方法正好相反,可以将数据从已有的ImageData对象绘制为位图。如果提供了一个绘制过的矩形,则只绘制该矩形的像素。

语法: putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)

参数:

  • ImageData:包含像素值的数组对象。
  • dx:源图像数据在目标画布中 x 轴方向的偏移量。
  • dy:源图像数据在目标画布中 y 轴方向的偏移量。
  • dirtyX:可选参数,在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(x 坐标)。
  • dirtyY:可选参数,在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(y 坐标)。
  • dirtyWidth:可选参数,在源图像数据中,矩形区域的宽度。默认是图像数据的宽度。
  • dirtyHeight:可选参数,在源图像数据中,矩形区域的高度。默认是图像数据的高度。
uid:q3U7if
VOIDIS.ME
  1. no-like
  2. message
  3. Bilibili
  4. Github
  5. RSS
  6. sun