HTML Canvas 基础

<canvas> ​是一个可以使用脚本绘制图形的元素。它可以用于绘制图表、图片和制作简单的动画

基本用法

canvas 元素

<canvas id="tutorial" width="150" height="150"></canvas>

<canvas>​ 默认宽度为 300 像素,高度为 150 像素。它可以使用 CSS 来设置宽高,但是如果使用 CSS 设置的宽高和使用 width​、height​ 设置的宽高比例不一致,那么绘制出来的图像会出现扭曲,因此不建议使用 CSS 来为画布设置宽高

渲染上下文

<canvas>​ 元素创建了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。脚本需要找到渲染上下文才能在画布上绘制。<canvas>​ 元素有一个 getContext()​ 方法,它可以用来获取渲染上下文和绘画功能。getContext()​ 接受一个参数,即上下文的类型。

//获取画布元素
let canvas = document.getElementById('tutorial');
//获取2d渲染上下文
let ctx = canvas.getContext('2d');

绘制图形

栅格

image

<canvas>​ 元素默认被栅格覆盖,栅格中的一个单元对应一个像素点,栅格的起始点为左上角。所有元素都是根据起始点确定位置。

绘制矩形

<canvas>​ 只支持两种形式的图形绘制:矩形和路径(由点连成的线段)。所有其他类型的图形都是通过一条或多条路径组合而成的。

矩形有三种绘制方法:

  • fillRect(x,y,width,height)​:绘制一个填充矩形
  • strokeRect(x,y,width,height)​:绘制一个矩形的边框
  • clearRect(x,y,width,height)​:清除指定矩形区域,让清除部分完全透明

它们三个方法拥有相同的参数,x​ ​和 y​ ​指定了所绘制的矩形的左上角,width​ ​和 height​ ​设置了矩形宽高

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}

上面代码绘制的图像如下:

​​image

绘制路径

路径是由点连成的线段。使用路径绘制图形的步骤

  1. 创建路径的起始点
  2. 使用画图命令画出路径
  3. 把路径封闭
  4. 使用描边或填充路径区域来渲染图形

以下是所要用到的函数:

  • beginPath()​:清空子路径列表开始一个新的路径
  • closePath()​:是画笔返回当前子路径的起始点,它尝试从当前点到起始点绘制一条直线。如果子路径已经是封闭的或者自由一个点,那么方法不做任何操作
  • stroke()​:使用当前的样式描边子路径
  • fill()​:使用当前的样式填充子路径

下面的代码示例绘制了一个实心三角形:

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    ctx.beginPath();
    ctx.moveTo(75, 50);
    ctx.lineTo(100, 75);
    ctx.lineTo(100, 25);
    ctx.fill();
  }
}

结果如下:

image

调用 fill() ​函数,所有没有闭合的路径都会自动闭合,因此 closePath() ​不是必须的,因为当路径闭合时,它什么都不会做。

更多的绘制路径的方法请参考 CanvasRenderingContext2D - Web API 接口参考 | MDN (mozilla.org)

Path2D 对象

​Path2D​对象用来记录绘画命令,这样可以快速地回顾路径。它可以使用一个路径或者一个包含 SVG path 数据的字符串作变量

new Path2D();     // 空的 Path 对象
new Path2D(path); // 克隆 Path 对象
new Path2D(d);    // 从 SVG 建立 Path 对象

下面这个例子,我们创建了一个矩形和一个圆形,将它们存到 Path2D 对象中,然后使用 stroke() ​和 fill() ​将对象画到画布中

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
    var ctx = canvas.getContext('2d');

    //矩形
    var rectangle = new Path2D();
    rectangle.rect(10, 10, 50, 50);

    //圆形
    var circle = new Path2D();
    circle.moveTo(125, 35);
    circle.arc(100, 35, 25, 0, 2 * Math.PI);

    //描边矩形
    ctx.stroke(rectangle);
    //填充圆形
    ctx.fill(circle);
  }
}

上面的代码绘制的结果如下:

image

使用样式和颜色

我们可以设置渲染上下文来设置图形的样式和颜色

色彩 Colors

  • fillStyle​:设置图形填充颜色
  • strokeStyle​:设置图形描边颜色

这两个属性的值都是 CSS 表示颜色值的字符串。

// 这些 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.strokeStyle= "rgb(255,165,0)";
ctx.strokeStyle= "rgba(255,165,0,1)";

透明度 Transparency

通过设置 globalAlpha ​属性或使用具有透明度的颜色来绘制半透明图形

//设置globalAlpha属性
ctx.globalAlpha = 0.2;

// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";

线型 Line styles

属性可能的值描述
lineWidth正数线条粗细
lineCapbutt​、round​、square线条末端样式
lineJoinbevel​、round​、miter线条与线条连接处的样式
miterLimit正数限制两条线相交时交接处最大长度
setLineDash数组设置虚线样式
lineDashOffsetfloat 精度的数字,默认为 0.0设置虚线偏移量

线条是如何画出的

image

如图所示,在第一个图中,填充了 (2,1) 至 (5,5) 的矩形,整个区域的边界刚好落在像素边缘上,这样就可以得到的矩形有着清晰的边缘。

如果你想要绘制一条从 (3,1) 到 (3,5),宽度是 1.0 的线条,你会得到像第二幅图一样的结果,像素是显示的最小单位,没办法渲染半个,所以它会使用笔触一半的颜色填充(深蓝色和浅蓝色的部分),这样的线条是不准确的

要解决这个问题,你必须对路径施以更加精确的控制。已知粗 1.0 的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5,1) 到 (3.5,5) 的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0 的线条。

渐变 Gradients

  • createLinearGradient(x1, y1, x2, y2)​​:接受 4 个参数,表示渐变的起点和终点

  • createRadialGradient(x1, y1, r1, x2, y2, r2)​:接受 6 个参数,x1​、y1 ​定义圆心位置,r1 ​设置半径

  • gradient.addColorStop(position,color)​:给渐变设置颜色

    • position​ 参数是一个 0.0 到 1.0 之间的数值,表示颜色在 CanvasGradient​ 对象内的位置。
    • color ​参数是以一个有效的 CSS 颜色值

使用以上两个方法新建一个 CanvasGradient​ 对象,赋给图形的 fillStyle​ 或 strokeStyle​ 属性。然后用 gradient.addColorStop(position,color)​ 设置颜色。下面是两个渐变效果图。

image

图案样式 Patterns

createPattern(image,type) ​方法接受两个参数,image ​参数可以是一个 image 对象的引用,或者另一个 canvas 对象,type ​参数必须是以下字符串值之一:repeat​、repeat-x​、repeat-y​、no-repeat​。

图案的应用和渐变类似,创建一个 CanvasPattern ​对象,然后赋值给 fillStyle ​或 strokeStyle ​属性即可。

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 创建新 image 对象,用作图案
  var img = new Image();
  img.src = 'canvas_createpattern.png';
  img.onload = function() {

    // 创建图案
    var ptrn = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = ptrn;
    ctx.fillRect(0, 0, 150, 150);

  }
}

上面代码绘制的结果如下:

image

使用 Image 对象的 onload ​事件来确保设置图案前图像已经装载完毕,否者可能出现图案效果不对的情况

阴影 Shadows

属性可能的值描述
shadowOffsetX浮点型数值设定阴影在 x 轴的延伸距离,正值向右延伸,负值向左
shadowOffsetY浮点型数值设定阴影在 y 轴的延伸距离,正值向下延伸,负值向上
shadowBlur浮点数值设置阴影的模糊程度
shadowColor标准的 CSS 颜色值设置阴影颜色效果,默认是全透明的黑色
function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 2;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

  ctx.font = "20px Times New Roman";
  ctx.fillStyle = "Black";
  ctx.fillText("Sample String", 5, 30);
}

上面的代码绘制的结果如下,一个带阴影效果的文字:

image

Canvas 填充规则

当我们用到 fill​、clip​、isPointinPath ​时我们可以选择一个填充规则,该规则根据某处在路径的外面或者里面来决定该处是否被填充。它有两个可能的值:nonzero​、evenodd

nonzero 非零规则

计算顺时针逆时针数量,计算结果不是 0,就填充;计算结果是 0,就不填充。

比如我们在下图取一点 B,向任意方向发出一条射线,找到和射线发生交叉的路径(注意路径方向,这和偏转的顺逆有关):

  • 发生逆时针的偏转(路径 3),记 -1
  • 发生顺时针偏转(路径 2),记 +1

计算结果是 0,所以不填充。

​​image​​

evenodd 奇偶规则

计算交叉路径数量,计算结果是奇数,就填充;计算结果是偶数,就不填充。

比如我们在下图取一点 A,向任意方向发送一条射线,找到和射线发生交叉的路径,计算结果是偶数,所以不填充。

image

本节内容参考 Canvas 保姆级教程(上):绘制篇 - 掘金 (juejin.cn)

绘制文本

canvas 提供了两种方法来渲染文本:

fillText(text, x, y [, maxWidth])​:在指定位置填充指定文本,绘制的最大宽度可选

strokeText(text, x, y [, maxWidth])​:在指定位置绘制文本边框,绘制的最大宽度可选

设置文本样式

属性可能的值描述
font和 CSS 相同的语法设置字体样式
textAlignstart​(默认值)、end​、left​、right​、center文本对齐选项
textBaselinetop​、hanging​、middle​、alphabetic​(默认值)、ideographic​、bottom基线对齐选项
directionltr​、rtl​、inherit​(默认值)文本方向

预测量文本宽度

可以使用 measureText() ​方法返回一个 Textetrice ​对象,这个对象包含文本的宽度、所在像素,这些体现文本特性的属性。

下面这段代码测量文本获取了它的宽度:

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var text = ctx.measureText("foo"); // TextMetrics object
  text.width; // 16;
}

使用图像

canvas 更有意思的意向特性就是图像操作能力。可以用于动态的图像合成或者作为图形的背景等。浏览器支持任意格式的外部图片使用。

获取需要绘制的图片

可以使用下面这些类型作为图片的源

  • HTMLImageElement​:图像元素
  • HTMLVideoElement​:视频元素
  • HTMLCanvasElement​:画布元素
  • ImageBitmap​​:位图

绘制图片

获取了源图对象,我们就可以使用 drawImage()​ 方法将它渲染到 canvas 里,它有三种不同参数的实现

  • drawImage(image, dx, dy)​:常规方法
  • drawImage(image, dx, dy, dWidth, dHeight)​:缩放方法
  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)​:切片方法

image

根据上图来理解 drawImage() ​方法的参数

参数描述
image​​图像源
sx​​矩形选择框左上角的 x​ 轴坐标
sy​​矩形选择框左上角的 y​ 轴坐标
sWidth​​切片时矩形选择框的宽度
sHeight​​切片时矩形选择框的高度
dx​​image​​ 在画布上的 x 轴坐标
dy​​image​​ 在画布上的 y 轴坐标
dWidth​​image​​ 在画布上的宽度
dHeight​​image​​ 在画布上的高度

下面是一个简单的示例:

  function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');
    var img = new Image();
    img.onload = function(){
      ctx.drawImage(img,0,0);
      ctx.beginPath();
      ctx.moveTo(30,96);
      ctx.lineTo(70,66);
      ctx.lineTo(103,76);
      ctx.lineTo(170,15);
      ctx.stroke();
    }
    img.src = 'backdrop.png';
  }

运行结果如下图:

image

控制图像的缩放行为

过度的缩放图像可能会导致图像模糊或者像素化。可以通过 imageSmoothingEnabled ​属性来控制是否在缩放图像时使用平滑算法。默认值为 true

变形

变形可以改变原点位置,对网格进行旋转和缩放。

状态的保存和恢复

  • save()​:保存 canvas 状态
  • restore()​:恢复 canvas 状态

这两个方法和 word 的保存和撤销一样

移动

translate(x, y)​ 方法用来移动 canvas 和它的原点,它有两个参数:x​ 是左右偏移量,y​ 是上下偏移量,如下图所示:

image

旋转

rotate(angle)​ 方法用来以原点为中心旋转 canvas,angle​ 是以顺时针方向旋转的角度

image

缩放

scale(x,y)​ 方法的两个参数都是可以为负数的实数,x​ 为水平缩放因子,y​ 为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。默认值为 1,为实际大小。

画布初始情况下,是以左上角坐标为原点的第一象限。如果参数为负实数,相当于以 x 或 y 轴作为对称轴镜像反转

变形

变形是将平移、缩放、旋转等合为一体,使用矩阵来操作几何变化,变形针对的是画布本身。矩阵的概念可以看这一篇:简单教程

transform(a, b, c, d, e, f)​ 将当前的变形矩阵乘以上一个矩阵

  • a​​:水平方向的缩放
  • b​​​:竖直方向的倾斜偏移
  • c​​​:水平方向的倾斜偏移
  • d​​​:竖直方向的缩放
  • d​​​:水平方向的移动
  • f​​​:竖直方向的移动

参数 b​ 和 c​ 用于斜拉画布,详见 “Canvas 斜拉 - Canvas 基础教程”[^1]

下面是一个示例代码:

ctx.fillStyle="orange";
ctx.fillRect(50,50,100,50);
ctx.transform(1,1,0,1,0,0);
ctx.fillStyle="green";
ctx.fillRect(50,50,100,50);

结果:

​[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IOpZtqrO-1681779147762)(https://assets.b3logfile.com/siyuan/1622442485157/assets/image-20230417143247-79nqvwq.png)]​

setTransform(a, b, c, d, e, f) ​将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform ​方法。

resetTransform()​ 重置当前变形为单位矩阵,它和 ctx.setTransform(1, 0, 0, 1, 0, 0)​ 等效。

组合与裁切

组合

globalCompositeOperation​​ 提供了 12 种合成图形的方式:

属性值描述效果
source-over​​默认设置,直接在现有内容上绘制新图形​​image
source-in​​只在重叠部分绘制新图形,其他部分是透明的​​image
source-out​​在不与现有内容重叠的地方绘制新图形​​image
source-atop​​新图形只在与现有内容重叠的地方绘制​​image
destination-over​​在现有内容底下绘制新图形​​image
destination-in​​只在重叠部分底下绘制新图形​​image
destination-out​​在不与现有内容重叠的地方保持现有内容​​image
destination-atop​​现有内容只保留重叠部分,新图形在现有
内容底下绘制
​​image
lighter​​重叠部分的颜色通过颜色值相加来确定​​image
copy​​只显示新图形​​image
xor​​重叠和正常绘制之外的地方是透明的​​image
multiply​​将顶层像素和底层像素相乘,结果是重叠
部分更黑暗的图片
​​image
screen​​像素被倒转,相乘,再倒转,结果是一幅
更明亮的图片
​​image
overlay​​multiply 和 screen 的结合,原本暗的地方
更暗,原本亮的地方更亮
​​image
darken​​保留两个图层中最暗的像素​​image
lighten​​保留两个图层中最亮的像素​​image
color-dodge​​将底层除以顶层的反置​​image
color-burn​​将反置的底层除以顶层,然后将结果反过来​​image
hard-light​​屏幕相乘类似于叠加,但是上下图层互换了​​image
soft-light​​用顶层减去底层或反过来得到一个正值​​image
difference​​一个柔和版本的 hard-light​​​​image
exclusion​​difference​ ​相似,但对比度比较低​​image
hue​​保留了底层的亮度和色度,同时采用了顶层的
色调
​​image
saturation​​保留了底层的亮度和色度,同时采用了顶层的
色度
​​image
color​​保留了底层的亮度,同时采用了顶层的色调和
色度
​​image
luminosity​​保持底层的色调和色度,同时采用顶层的亮度​​image

裁切

裁切的作用是用来隐藏不需要的部分。如下图显示,五角星外部的部分不会在画布上绘制出来

image

clip​是绘制图形的第三个方法,它的作用是将当前正在构建的路径转换为当前的裁切路径

参考链接

Canvas 教程 - Web API 接口参考 | MDN (mozilla.org)

CanvasRenderingContext2D - Web API 接口参考 | MDN (mozilla.org)

Canvas 保姆级教程(上):绘制篇 - 掘金 (juejin.cn)

Canvas 变形 transform() - Canvas 基础教程 - 简单教程,简单编程 (twle.cn)

Canvas 图像混排模式 - Canvas 基础教程 - 简单教程,简单编程 (twle.cn)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值