Canvas | 青训营笔记

191 阅读11分钟

这是我参与「第四届青训营 」笔记创作活动的第10天

(使用 SVG path data 创建一个 Path2D 路径未完结,在path2D板块)

Canvas API 提供了<canvas>元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

Canvas API 主要聚焦于 2D 图形。而同样使用<canvas>元素的 WebGL API 则用于绘制硬件加速的 2D 和 3D 图形。

Canvas 的默认大小为 300 像素×150 像素(宽×高,像素的单位是 px)。但是,可以使用 HTML 的高度和宽度属性来自定义 Canvas 的尺寸。为了在 Canvas 上绘制图形,使用一个 JavaScript 上下文对象,它能动态创建图像

Canvas接口:

<canvas> 标签只有两个属性—— widthheight

渲染上下文:

canvas 起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas> 元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()接受一个参数,即上下文的类型。对于 2D 图像而言,可以使用 CanvasRenderingContext2D

var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');

模板骨架(显示为一个2D正方形):

<html>
  <head>
    <title>Canvas tutorial</title>
    <script type="text/javascript">
      function draw(){
        var canvas = document.getElementById('tutorial');
        if (canvas.getContext){
          var ctx = canvas.getContext('2d');
        }
      }
    </script>
    <style type="text/css">
      canvas { border: 1px solid black; }
    </style>
  </head>
  <body onload="draw();">
    <canvas id="tutorial" width="150" height="150"></canvas>
  </body>
</html>

绘制图形

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

绘制矩形:

x 与 y 指定了在 canvas 画布上所绘制的矩形的左上角(相对于原点)的坐标。width 和 height 设置矩形的尺寸。

绘制路径:

路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。

基础:
  • lineWidth:线段宽度
  • beginPath()
  • moveTo(x, y)
  • lineTo(x, y)
  • closePath()
  • stroke():通过线条来绘制图形轮廓。
  • fill()通过填充路径的内容区域生成实心的图形。
  • arc(x, y, radius, startAngle, endAngle, anticlockwise)画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle (用弧度表示,弧度=(Math.PI/180)×角度。)结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成,为 true 时,是逆时针方向,否则顺时针方向。
  • arcTo(x1, y1, x2, y2, radius) 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。
方法
绘制矩形位图
绘制文本
线型
文本样式
填充和描边样式
渐变和图案
阴影
路径
  • CanvasRenderingContext2D.beginPath()清空子路径列表开始一个新的路径。当你想创建一个新的路径时,调用此方法。

  • CanvasRenderingContext2D.closePath()使笔点返回到当前子路径的起始点。它尝试从当前点到起始点绘制一条直线。如果图形已经是封闭的或者只有一个点,那么此方法不会做任何操作。

  • CanvasRenderingContext2D.moveTo() 将一个新的子路径的起始点移动到 (x,y) 坐标。

  • CanvasRenderingContext2D.lineTo() 使用直线连接子路径的最后的点到 x,y 坐标。

  • CanvasRenderingContext2D.bezierCurveTo()添加一个 3 次贝赛尔曲线路径。该方法需要三个点。 第一、第二个点是控制点,第三个点是结束点。起始点是当前路径的最后一个点,绘制贝赛尔曲线前,可以通过调用 moveTo() 进行修改。

  • CanvasRenderingContext2D.quadraticCurveTo() 添加一个 2 次贝赛尔曲线路径。

  • CanvasRenderingContext2D.arc() 绘制一段圆弧路径, 圆弧路径的圆心在  (x, y)  位置,半径为 r ,根据anticlockwise (默认为顺时针)指定的方向从 startAngle 开始绘制,到 endAngle 结束。

  • CanvasRenderingContext2D.arcTo() 根据控制点和半径绘制圆弧路径,使用当前的描点 (前一个 moveTo 或 lineTo 等函数的止点)。根据当前描点与给定的控制点 1 连接的直线,和控制点 1 与控制点 2 连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径。

  • CanvasRenderingContext2D.ellipse() 添加一个椭圆路径,椭圆的圆心在(x,y)位置,半径分别是radiusX 和 radiusY ,按照anticlockwise (默认顺时针)指定的方向,从 startAngle 开始绘制,到 endAngle 结束。

  • CanvasRenderingContext2D.rect() 创建一个矩形路径,矩形的起点位置是  (x, y)  ,尺寸为 width 和 height

绘制路径
变换

在 CanvasRenderingContext2D 渲染背景中的对象会有一个当前的变换矩阵,一些方法可以对其进行控制。当创建当前的默认路径,绘制文本、图形和Path2D对象的时候,会应用此变换矩阵。下面列出的方法保持历史和兼容性的原因,是为了SVGMatrix对象现在能够应用于大部分 API ,将来会被替换。

合成
绘制图像
像素控制

参见 ImageData 对象。

图像平滑
canvas 状态

CanvasRenderingContext2D 渲染环境包含了多种绘图的样式状态(属性有线的样式、填充样式、阴影样式、文本样式)。下面的方法会帮助你使用这些状态:

点击区域
  • CanvasRenderingContext2D.addHitRegion()给 canvas 添加点击区域。

  • [CanvasRenderingContext2D.removeHitRegion()]从 canvas 中删除指定 id 的点击区域。

  • [CanvasRenderingContext2D.clearHitRegions()]从 canvas 中删除所有的点击区域。

二次贝塞尔曲线及三次贝塞尔曲线

用来绘制复杂有规律的图形

  • quadraticCurveTo(cp1x, cp1y, x, y)绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点。

  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) 绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

绘制矩形
  • rect(x, y, width, height) 绘制一个左上角坐标为(x,y),宽高为 width 以及 height 的矩形。

Path2D对象

会返回一个新初始化的 Path2D 对象(可能将某一个路径作为变量——创建一个它的副本,或者将一个包含 SVG path 数据的字符串作为变量),可选参数有- path (当调用另一个 Path2D 对象时,会创建一个 path 变量的拷贝)和 d (当调用 SVG path 数据构成的字符串时,会根据描述创建一个新的路径)

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

例如

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var path1 = new Path2D();
path1.rect(10, 10, 100,100);

var path2 = new Path2D(path1);
path2.moveTo(220, 60);
path2.arc(170, 60, 50, 0, 2 * Math.PI);

ctx.stroke(path2);
使用SVG路径

使用 SVG path data 创建一个 Path2D 路径

使用样式和颜色

色彩

color 可以是表示 CSS 颜色值的字符串,渐变对象或者图案对象

透明度

  • globalAlpha = transparencyValue这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0(完全透明)到 1.0(完全不透明),默认是 1.0,从设置透明度往下的所有都会被设置成该透明度。但使用符合 CSS 3 规范的颜色值rgba更好

线型

  • lineWidth = value设置线条宽度。

  • lineCap = type设置线条末端样式,buttround 和 square。默认是 butt。

  • lineJoin = type设定线条与线条间接合处的样式,roundbevel 和 miter。默认是 miter

  • miterLimit = value限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度。

  • getLineDash()返回一个包含当前虚线样式,长度为非负偶数的数组。

  • setLineDash(segments)设置当前虚线样式,用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式。setLineDash 方法接受一个数组,来指定线段与间隙的交替;lineDashOffset 属性设置起始偏移量。。

  • lineDashOffset = value设置虚线样式的起始偏移量。

渐变

  • createLinearGradient(x1, y1, x2, y2)createLinearGradient 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。

  • createRadialGradient(x1, y1, r1, x2, y2, r2)createRadialGradient 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。

  • gradient.addColorStop(position, color)addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF,rgba(0,0,0,1),等等)

图案样式

  • createPattern(image, type) 该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeatrepeat-xrepeat-y 和 no-repeat
var img = new Image();
img.src = 'someimage.png';
var ptrn = ctx.createPattern(img,'repeat');

阴影

  • shadowOffsetX = float shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0

  • shadowOffsetY = floatshadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0

  • shadowBlur = float shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0

  • shadowColor = color shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。

绘制文本

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

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

  • font = value当前我们用来绘制文本的样式。这个字符串使用和 CSS font 属性相同的语法。默认的字体是 10px sans-serif

  • textAlign = value 文本对齐选项。可选的值包括:startendleftright or center. 默认值是 start

  • textBaseline = value基线对齐选项。可选的值包括:tophangingmiddlealphabeticideographicbottom。默认值是 alphabetic。

  • direction = value- measureText()将返回一个 TextMetrics对象的宽度、所在像素,这些体现文本特性的属性。文本方向。可能的值包括:ltrrtlinherit。默认值是 inherit。

  • measureText() 将返回一个 TextMetrics对象的宽度、所在像素,这些体现文本特性的属性。

使用图像

  • HTMLImageElement 这些图片是由Image()函数构造出来的,或者任何的<img>元素

  • HTMLVideoElement 用一个 HTML 的 <video>元素作为你的图片源,可以从视频中抓取当前帧作为一个图像

  • HTMLCanvasElement 可以使用另一个 <canvas> 元素作为你的图片源。

  • ImageBitmap 这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其它几种源中生成。

在 HTMLImageElement上使用crossOrigin (en-US)属性,你可以请求加载其它域名上的图片。如果图片的服务器允许跨域访问这个图片,那么你可以使用这个图片而不污染 canvas,否则,使用这个图片将会污染 canvas

  • drawImage(image, x, y) 其中 image 是 image 或者 canvas 对象,x 和 y 是其在目标 canvas 里的起始坐标。

  • drawImage(image, x, y, width, height)缩放,这个方法多了 2 个参数:width 和 height,这两个参数用来控制 当向 canvas 画入时应该缩放的大小

通过onload触发:

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var img = new Image();
  img.onload = function(){
    for (var i=0;i<4;i++){
      for (var j=0;j<3;j++){
        ctx.drawImage(img,j*50,i*38,50,38);
      }
    }
  };
  img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
}

过度缩放图像可能会导致图像模糊或像素化。您可以通过使用绘图环境的imageSmoothingEnabled属性来控制是否在缩放图像时使用平滑算法。默认值为true,即启用平滑缩放。禁用此功能:

ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;

变形

  • save()保存画布 (canvas) 的所有状态

  • restore()save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数

  • translate(x, y)偏移,接受两个参数。*x *是左右偏移量,y 是上下偏移量

  • rotate(angle)旋转,只接受一个参数:旋转的角度 (angle),它是顺时针方向的,以弧度为单位的值。 旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 translate 方法。

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

  • transform(a, b, c, d, e, f)变形,这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵

a (m11)水平方向的缩放

b(m12)竖直方向的倾斜偏移

c(m21)水平方向的倾斜偏移

d(m22)竖直方向的缩放

e(dx)水平方向的移动

f(dy)竖直方向的移动

  • setTransform(a, b, c, d, e, f) 这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。

  • resetTransform()重置当前变形为单位矩阵,它和调用以下语句是一样的:ctx.setTransform(1, 0, 0, 1, 0, 0);

组合

  • globalCompositeOperation = type这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识 12 种遮盖方式的字符串,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(屏幕相乘(A combination of multiply and screen)类似于叠加,但上下图层互换了)soft-light(用顶层减去底层或者相反来得到一个正值)difference (一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白)exclusion(和 difference 相似,但对比度较低)hue(保留了底层的亮度(luma)和色度(chroma),同时采用了顶层的色调(hue)saturation(保留底层的亮度(luma)和色调(hue),同时采用顶层的色度(chroma))color(保留了底层的亮度(luma),同时采用了顶层的色调 (hue) 和色度 (chroma))luminosity(保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma))

剪裁

  • clip()将当前正在构建的路径转换为当前的裁剪路径。

基本动画

步骤:清空canvas、保存canvas状态、绘制动画图形、恢复canvas状态

动画示例 时钟:

function clock(){
  var now = new Date();
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.save();
  ctx.clearRect(0,0,150,150);
  ctx.translate(75,75);
  ctx.scale(0.4,0.4);
  ctx.rotate(-Math.PI/2);
  ctx.strokeStyle = "black";
  ctx.fillStyle = "white";
  ctx.lineWidth = 8;
  ctx.lineCap = "round";

  // Hour marks
  ctx.save();
  for (var i=0;i<12;i++){
    ctx.beginPath();
    ctx.rotate(Math.PI/6);
    ctx.moveTo(100,0);
    ctx.lineTo(120,0);
    ctx.stroke();
  }
  ctx.restore();

  // Minute marks
  ctx.save();
  ctx.lineWidth = 5;
  for (i=0;i<60;i++){
    if (i%5!=0) {
      ctx.beginPath();
      ctx.moveTo(117,0);
      ctx.lineTo(120,0);
      ctx.stroke();
    }
    ctx.rotate(Math.PI/30);
  }
  ctx.restore();

  var sec = now.getSeconds();
  var min = now.getMinutes();
  var hr  = now.getHours();
  hr = hr>=12 ? hr-12 : hr;

  ctx.fillStyle = "black";

  // write Hours
  ctx.save();
  ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec )
  ctx.lineWidth = 14;
  ctx.beginPath();
  ctx.moveTo(-20,0);
  ctx.lineTo(80,0);
  ctx.stroke();
  ctx.restore();

  // write Minutes
  ctx.save();
  ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec )
  ctx.lineWidth = 10;
  ctx.beginPath();
  ctx.moveTo(-28,0);
  ctx.lineTo(112,0);
  ctx.stroke();
  ctx.restore();

  // Write seconds
  ctx.save();
  ctx.rotate(sec * Math.PI/30);
  ctx.strokeStyle = "#D40000";
  ctx.fillStyle = "#D40000";
  ctx.lineWidth = 6;
  ctx.beginPath();
  ctx.moveTo(-30,0);
  ctx.lineTo(83,0);
  ctx.stroke();
  ctx.beginPath();
  ctx.arc(0,0,10,0,Math.PI*2,true);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(95,0,10,0,Math.PI*2,true);
  ctx.stroke();
  ctx.fillStyle = "rgba(0,0,0,0)";
  ctx.arc(0,0,3,0,Math.PI*2,true);
  ctx.fill();
  ctx.restore();

  ctx.beginPath();
  ctx.lineWidth = 14;
  ctx.strokeStyle = '#325FA2';
  ctx.arc(0,0,142,0,Math.PI*2,true);
  ctx.stroke();

  ctx.restore();

  window.requestAnimationFrame(clock);
}

window.requestAnimationFrame(clock);

高级动画

结合js编程,示例小球

像素操作

ImageData对象中存储着 canvas 对象真实的像素数据,它包含以下几个只读属性:

  • width图片宽度,单位是像素

  • height图片高度,单位是像素

  • data Uint8ClampedArray 类型的一维数组,包含着 RGBA 格式的整型数据,范围在 0 至 255 之间,每个像素用 4 个 1bytes 值 (按照红,绿,蓝和透明值的顺序; 这就是"RGBA"格式) 例如,要读取图片中位于第 50 行,第 200 列的像素的蓝色部份

blueComponent = imageData.data[((50 * (imageData.width * 4)) + (200 * 4)) + 2];

根据行、列读取某像素点的 R/G/B/A 值的公式:

imageData.data[((50 * (imageData.width * 4)) + (200 * 4)) + 0/1/2/3];

创建一个ImageData对象

createImageData()

var myImageData = ctx.createImageData(width, height);

var myImageData = ctx.createImageData(anotherImageData);

得到场景像素数据

getImageData()

var myImageData = ctx.getImageData(left, top, width, height);

写入场景像素数据

putImageData()

ctx.putImageData(myImageData, dx, dy);

dx 和 dy 参数表示希望在场景内左上角绘制的像素数据所得到的设备坐标。

缩放和反锯齿

保存图片