循序渐进学习canvas -- 长文预警

258 阅读14分钟

canvas是一个HTML标签,必须结合JS代码绘制和处理一些图形。

基本涉及到图片处理的相关操作,比如截图功能、合成图片、图片像素级处理、图片的动画,都可以考虑下canvas

大名鼎鼎的echartsd3.jsthree.js可视化数据的这些库都是基于canvas。

哦,还能解决大量图片相关面试题,如实现动画时钟。

动画时钟

TL;DR

  • 特基础知识:创建画布、获取渲染上下文、绘制矩形、绘制三角形、绘制圆弧、绘制文本
  • 突然发现慕课上这个老师关于基础这里,讲的很好,第一章第二章
  • 设置图形的各种样式,如背景、线、颜色和透明度
  • 高阶知识:缓存路径、保存和恢复状态、移动坐标原点、旋转坐标轴、缩放网格、transform的使用
  • 重点-操作图像:合成、裁剪、动画、控制每个像素、上传和下载

创造画布

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

上述代码,canvas就创造了一个 150*150 像素的画布了。

canvasimg看起来很像,但其只有两个特有属性widthheight,默认值是 300、150。
因为 canvas 是一个标签元素,自然可以使用属性设置,也可使用 css 设置样式。
注意的是,如果 CSS 的尺寸与初始画布的比例不一致,它会出现扭曲,这时候就需要明确用属性标明宽高。

获取渲染上下文

画布创建好,要想绘制图形,需要结合 JS。

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
  • JS 先找到画布 canvas
  • 然后用 canvas 的getContext(),来获得渲染上下文和它的绘画功能。其参数是上下文的格式,一般都是2d

这里说个基础点,canvas 画布,想象成格子本,每个格子是 1*1 像素,左上角的点是(0,0),其他的点都有其相应的坐标(x,y)。

绘制矩形

<!-- 这里style是为了显示画布大小,这样更好识别(x,y) -->
<canvas
  id="tutorial"
  width="150"
  height="150"
  style="border:1px solid #ccc;"
></canvas>
<script>
  var canvas = document.getElementById("tutorial");
  var ctx = canvas.getContext("2d");
  ctx.fillRect(25, 25, 100, 100);
</script>

fillRect(x, y, width, height),用颜色填充出一个矩形,x 和 y 是矩形左上角的坐标,width 和 height 是矩形宽高。

矩形

当然绘制矩形,canvas 提供三种方法,上面是一个,下面两个类似(还有一个方法在后面)

  • strokeRect(x, y, width, height),用线条绘制一个矩形轮廓
  • clearRect(x, y, width, height),像橡皮,清除指定矩形区域,让清除部分完全透明
var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);

矩形

绘制三角形

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.closePath();
ctx.stroke();
// ctx.fill();

三角形

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

使用路径绘制图形的步骤:

  • 准备绘制一个新的形状路径,ctx.beginPath()
  • 将笔触移动到指定位置,ctx.moveTo(75, 50)
  • 绘制从当前位置到指定位置的直线。ctx.lineTo(100, 75);
  • 绘制从当前位置到起始笔触的直线,形成封闭图形,路径结束,ctx.closePath()
  • 一旦路径生成,你就能通过描边或填充路径区域来渲染图形,ctx.fill()ctx.stroke()

小提示,调用 fill()函数时,所有没有闭合的形状都会自动闭合,所以可省略 closePath(),但 stroke 不行哟。

想绘制两个三角形的话,重新启动beginPath开始画就可以了

// ...之前代码
ctx.beginPath();
ctx.moveTo(150, 150);
ctx.lineTo(150, 40);
ctx.lineTo(50, 150);
ctx.closePath();
ctx.fill();

三角形

绘制圆弧

// 角度换算成弧度
function getRadianFromAngle(angle) {
  return (Math.PI / 180) * angle;
}
var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
ctx.beginPath();
// 顺时针
ctx.arc(75, 75, 32, 0, getRadianFromAngle(90), false);
ctx.closePath();
ctx.stroke();

ctx.beginPath();
// 逆时针
ctx.arc(75, 75, 32, 0, getRadianFromAngle(135), true);
ctx.closePath();
ctx.fill();

弧形

arc(x, y, radius, startRadian, endRadian, anticlockwise),画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startRadian开始到endRadian结束,按照anticlockwise给定的方向(anti 是反对的,clockwise 顺时针的,anticlockwise 逆时针,默认值是 false 也就是顺时针)来生成。

说人话:想象拿一个圆规,先确定圆心,圆规一个脚站好,在确定半径,圆规另一个脚拉开特定距离。0 度是圆心的右边,90 度是圆心的下边,180 度是圆心的左边,270 度是圆心的上边,360 度回归右边。画 0 到 90 度,顺时针的话是 1/4 圆,逆时针的话是 3/4 的圆,想象圆规开始画的情景。

注意这里的 arc 只是绘制路径,想要看到需要借助 fill 或者 stroke。

同理,这里说下矩形的另一个方法,rect(x, y, width, height),相比fillRect(x, y, width, height)不一样的是,fillRect是自动填充出一个矩形,这里的rect只是绘制出路径,还需要用fillstroke才能看到矩形。换言之,fillRect = rect +fillstrokeRect = rect + stroke

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

绘制文本

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

ctx.font = "28px serif";
ctx.fillText("Hello world", 10, 30);

ctx.font = "28px serif";
ctx.strokeText("Hello world", 10, 80);

var text = ctx.measureText("foo");
// 37
console.log(text.width);

字

  • fillText(text, x, y [, maxWidth])在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.
  • strokeText(text, x, y [, maxWidth])在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.
  • measureText()返回一个 TextMetrics 对象的宽度所在像素,这些体现文本特性的属性。
  • font = value,用来绘制文本的样式. 这个字符串使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif
  • textAlign = value,文本对齐选项. 可选的值包括:start, end, left, right or center. 默认值是 start。
  • textBaseline = value,基线对齐选项. 可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。
  • direction = value,文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。
  • shadowOffsetX = float 设定阴影在 X 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,默认为 0。
  • shadowOffsetY = float 设定阴影在 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,默认为 0。
  • shadowBlur = float用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0。
  • shadowColor = color用于设定阴影颜色效果,默认是全透明的黑色。

设置不同的颜色和透明度

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
// 必须提前写好颜色
ctx.fillStyle = "rgba(0,255,0,0.5)";
ctx.rect(25, 25, 30, 30);
ctx.fill();

// !!!这里需要重置下路径哈,不然会和上面的混合在一起了
ctx.beginPath();
ctx.strokeStyle = "#FF0000";
ctx.globalAlpha = 0.5;
ctx.rect(80, 80, 40, 40);
ctx.stroke();

色

前期的 fill 和 stroke 都是黑色,需要别的颜色其实也很简单,想象你拿画笔,先选择特定颜色的画笔,然后画画就是特定颜色的了。这里的颜色同 css 的颜色,可以#ff0000redrgba(255,0,0,0.5)等。

  • fillStyle = color
  • strokeStyle = color
  • 透明度的话,一个是 rgba 模式,还有一个是globalAlpha = 某数值

设置渐变色

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

var lineGrad = ctx.createLinearGradient(0, 0, 0, 150);
lineGrad.addColorStop(0, "#00ABEB");
lineGrad.addColorStop(0.5, "#fff");
lineGrad.addColorStop(0.5, "#26C000");
lineGrad.addColorStop(1, "#fff");

ctx.strokeStyle = lineGrad;
ctx.rect(10, 10, 130, 130);
ctx.stroke();

ctx.beginPath();
// 径向渐变
var radialGrad = ctx.createRadialGradient(45, 45, 10, 52, 50, 30);
radialGrad.addColorStop(0, "#A7D30C");
radialGrad.addColorStop(0.9, "#019F62");
radialGrad.addColorStop(1, "rgba(1,159,98,0)");

ctx.fillStyle = radialGrad;
ctx.rect(0, 0, 150, 150);
ctx.fill();

渐变色

  • createLinearGradient(x1, y1, x2, y2),(x1,y1)渐变的起点与(x2,y2)终点
  • createRadialGradient(x1, y1, r1, x2, y2, r2),前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆
  • gradient.addColorStop(position, color),position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。

设置图案填充

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

// 创建新 image 对象,用作图案
var img = new Image();
img.src = "https://mdn.mozillademos.org/files/222/Canvas_createpattern.png";
img.onload = function() {
  // 创建图案
  var ptn = ctx.createPattern(img, "repeat");
  ctx.fillStyle = ptn;
  ctx.fillRect(0, 0, 150, 150);
};

图案

createPattern(image, type),Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat

设置线段的样式

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

ctx.lineWidth = 10;
ctx.lineJoin = "round";
ctx.setLineDash([40, 2]);
ctx.rect(20, 20, 100, 100);
ctx.stroke();

线段

线段的样式有以下,需要的时候,细细研究下即可:

  • lineWidth 线宽,是给定路径的中心到两边的粗细,注意最左边的以及所有宽度为奇数的线并不能精确呈现,这就是因为路径的定位问题。就是线宽的时候,需要考虑定位的问题。默认是 1。
  • lineCap 线端样式,butt,round 和 square。线端两端的帽子,butt 表示没帽子,round 是半圆帽子,square 是方块帽子。默认是 butt。
  • lineJoin 线端连接处样式,round, bevel 和 miter。连接处是怎么处理的,round 是边角被磨圆,bevel 是水平,miters 默认是 miter。
  • 用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式, setLineDash 方法接受一个数组,来指定线段与间隙的交替;lineDashOffset 属性设置起始偏移量。这个主要用在动画里。

缓存路径

var canvas = document.getElementById("tutorial");
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);

路径

Path2D对象,用来缓存或记录绘画命令,这样能快速地回顾路径。所有的路径方法比如 moveTo, rect, arc 等,都可以在 Path2D 中使用。

保存状态和恢复状态

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

// 画黑色,保存状态
ctx.fillRect(0, 0, 150, 150);
ctx.save();

// 换蓝色画一个,再保存状态
ctx.fillStyle = "#09F";
ctx.fillRect(15, 15, 120, 120);
ctx.save();

// 换白色画一个,不保存状态
ctx.fillStyle = "#FFF";
ctx.globalAlpha = 0.5;
ctx.fillRect(30, 30, 90, 90);

// 恢复上一个状态,也就是蓝色
ctx.restore();
ctx.fillRect(45, 45, 60, 60);

// 恢复上上一个状态,也就是黑色
ctx.restore();
ctx.fillRect(60, 60, 30, 30);

保存和恢复状态

Canvas 的状态就是当前画面应用的所有样式和变形的一个快照,而保存状态和恢复状态是绘制复杂图形时必不可少的方法:

  • save(),保存画布(canvas)的所有状态,无参数。当save()方法被调用后,当前的状态就被推送到栈中保存。状态包括之前提到过的各种属性、变形、裁切路径。
  • restore(),恢复 canvas 状态,无参数。当restore()方法被调用后,上一个保存的状态就从栈中弹出,相应的设定都恢复。

移动坐标原点

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

for (var i = 0; i < 3; i++) {
  // 将原始状态保存
  ctx.save();
  // 移动原点,画方块
  drawSquare(i);
  // 画完之后,重置回到原始状态
  ctx.restore();
}

function drawSquare(i) {
  ctx.fillStyle = "#f69";
  // 移动原点
  ctx.translate(10, 10 + i * 50);
  // 始终在(0,0)画
  ctx.fillRect(0, 0, 25, 25);
}

移动原点2 translate(x, y),x 是左右偏移量,y 是上下偏移量,如下图所示。
移动原点前记得保存状态,这样方便恢复状态,不然最后你可能会走到外太空。
移动 canvas 原点的好处:调整坐标之后,可以不用调整绘制的位置,这样很多时候更好理解和循环使用。
移动原点

旋转坐标轴

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

for (var i = 0; i < 3; i++) {
  // 将原始状态保存
  ctx.save();
  // 旋转坐标轴,画方块
  drawSquare(i);
  // 画完之后,重置回到原始状态
  ctx.restore();
}
// 角度换算成弧度
function getRadianFromAngle(angle) {
  return (Math.PI / 180) * angle;
}
function drawSquare(i) {
  ctx.fillStyle = `rgb(${51 * i},${255 - 51 * i},255)`;
  // 旋转坐标轴
  ctx.rotate(getRadianFromAngle(30 * i));
  // 始终在(0,0)画
  ctx.fillRect(0, 0, 80, 80);
}

旋转坐标轴

rotate(radian),旋转坐标轴的角度(以弧度为单位),它是顺时针方向的。旋转的中心点始终是坐标原点,如果要改变它,我们需要用到 translate 方法。同样记得旋转前保存下状态。

缩放网格

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

for (var i = 0; i < 3; i++) {
  // 将原始状态保存
  ctx.save();
  // 缩放网格,画方块
  drawSquare(i);
  // 画完之后,重置回到原始状态
  ctx.restore();
}

function drawSquare(i) {
  ctx.fillStyle = `rgb(${51 * i},${255 - 51 * i},255)`;
  // 缩放网格
  ctx.scale(1, (i + 1) * 1);
  // 同样是20*20,但是y轴上的1个网格本来表示1px,现在就相当于缩放的像素,表面看竖直方向被拉伸
  ctx.fillRect(0, 10 * i, 20, 20);
}

缩放网格

scale(x, y),可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比 1 小,会比缩放图形, 如果比 1 大会放大图形。默认值为 1, 为实际大小。
画布初始情况下, 是以左上角坐标为原点的第一象限。如果参数为负实数, 相当于以 x 或 y 轴作为对称轴镜像反转(例如, 使用 translate(0,canvas.height); scale(1,-1); 以 y 轴作为对称轴镜像反转, 就可得到著名的笛卡尔坐标系,左下角为原点)。

一次性设置原点、旋转、缩放

// ...
function drawSquare(i) {
  ctx.fillStyle = `rgb(${51 * i},${255 - 51 * i},255)`;
  // 缩放网格
  // ctx.scale(1,(i+1)*1);
  // 等同于
  ctx.transform(1, 0, 0, (i + 1) * 1, 0, 0);
  ctx.fillRect(0, 10 * i, 20, 20);
}
  • transform(a, b, c, d, e, f),这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵。a 水平方向的缩放,b 竖直方向的倾斜偏移,c 水平方向的倾斜偏移,d 竖直方向的缩放,e(dx)水平方向的移动,f(dy) 竖直方向的移动。如果任意一个参数是 Infinity,变形矩阵也必须被标记为无限大,否则会抛出异常。
  • setTransform(a, b, c, d, e, f),这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。
  • resetTransform()重置当前变形为单位矩阵,它和调用以下语句是一样的:ctx.setTransform(1, 0, 0, 1, 0, 0);

操作图像

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

// 方式
// html那边加一行代码    <img id='canvasImg' style='display: none;' src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/avatar-panda.png" alt="">
var domImg = document.querySelector("#canvasImg");
domImg.onload = function() {
  // 图片放在哪
  ctx.drawImage(domImg, 0, 0);
};

// 方式
var urlImg = new Image();
// 这里的src可以是
urlImg.src =
  "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/avatar-panda.png";
urlImg.onload = function() {
  // 图片放在哪,图片可以设置宽高
  ctx.drawImage(urlImg, 100, 0, 50, 50);
};

// 方式
var urlImg2 = new Image();
urlImg2.src =
  "https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/avatar-panda.png";
urlImg2.onload = function() {
  // 前四个参数是在原图片上,先找到剪切的点比如这里从图片左上角开始,然后剪切多长多宽如这里是50长宽
  // 后四个参数是将刚刚切出来的图片,放在画布的哪个位置比如100/60,然后在画布上面图片的宽高(比例不对图片会被拉伸)比如25/50
  ctx.drawImage(urlImg2, 0, 0, 50, 50, 100, 60, 25, 50);
};

图像 操作图像是 canvas 很重要的能力,可以合成新的图片等等。

引入图像到 canvas 需要两个步骤:

  • 1.获取图片源,图片源可以是
    • 已存在的 canvas
    • 已存在的 img 元素,
    • 也可以创建 Image 元素,其中的 src 可以是相对地址、绝对地址、data:url、视频的某帧
  • 2.使用 drawImage()函数将图片绘制到画布上,drawImage 有三种使用方式
    • context.drawImage(img,x,y);,在画布上定位图像
    • context.drawImage(img,x,y,width,height);,在画布上定位图像,并规定图像的宽度和高度
    • context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height),剪切图像,并在画布上定位被剪切的部分(s 是 slice 切片的意思)。

合成和裁剪

合成

globalCompositeOperation = type,如何将一个源(新的)图像绘制到目标(已有)的图像上。,其值是一个标识 12 种遮盖方式的字符串。默认值是source-over,在目标图像上显示源图像。 w3school 的对 12 种绘制方式解释的很到位

裁剪

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
// 如果准备裁剪,先存下初始状态,以方便重置
ctx.save();

// 画一个矩形轮廓
ctx.rect(50, 50, 40, 40);
ctx.stroke();

// 使用clip就表示之后的绘图,只有矩形范围内的才能看见
ctx.clip();

ctx.beginPath();
// 虽然矩形外也有,但是看不见
ctx.rect(40, 40, 30, 30);
ctx.fill();

// 恢复初始状态
ctx.restore();

裁剪

clip()将当前正在构建的路径转换为当前的裁剪路径,之后的绘图只会显示在裁剪路径内。在特定区域里绘制图形时相当好用。
一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。所以使用 clip() 方法前保存状态,方便之后恢复。

动起来

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
// 用对象将球的属性存起来,也方便使用
var ball = {
  x: 10,
  y: 10,
  vx: 5,
  vy: 5,
  radius: 10,
  color: "#f69",
  draw() {
    ctx.beginPath();
    // 画圆
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  }
};
var raf;
// 画每帧的关键函数
function draw() {
  // 清除画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  // 设置小球的下一次位置
  ball.x += ball.vx;
  ball.y += ball.vy;
  // 动画的关键
  raf = window.requestAnimationFrame(draw);
}
// 小球先显示出来
ball.draw();
// 鼠标在canvas的时候,动画启动
canvas.addEventListener("mouseover", function(e) {
  raf = window.requestAnimationFrame(draw);
});
// 动画取消
canvas.addEventListener("mouseout", function(e) {
  window.cancelAnimationFrame(raf);
});

动画

canvas 做动画也是挺炫酷的,做动画的基本步骤:

  • 清空 canvas。一般动画一帧一帧的画,如果不需要上一帧的话,直接清除整个画布即可ctx.clearRect(0, 0, canvas.width, canvas.height);
  • 适当保存 canvas 的状态。如果画图形的时候涉及到样式变形之类的,记得要保存状态,以便随时恢复。
  • 绘制每帧。这步大概率是重复进行的,以此实现动画的效果 setTimeout/setInterval/requestAnimationFrame
  • 恢复 canvas 的状态

当然还可以增加边界、加速度以及有个酷酷的小尾巴。 其他精彩动画,如实现时钟、贪吃蛇之类的也有详细代码。

控制每个像素的颜色

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
// 创建10*10像素的图片数据,data是数组,100*100*4的长度
var imgData = ctx.createImageData(100, 100);
// 每四项表示一个像素的颜色,分别表示rgba,换言之imgData.data.length始终是总网格的4倍
console.log(imgData);
for (var i = 0; i < imgData.data.length; i += 4) {
  setGridColor(imgData, i, 0, 255, 0, 255);
  // 超过一半就换个颜色
  if (i >= imgData.data.length / 2) {
    setGridColor(imgData, i, 255, 0, 0, 255);
  }
}
ctx.putImageData(imgData, 10, 10);

// [0,255,0,255]
console.log(getGridColor(imgData, 11, 11));

// 得到位于(100,100)位置的10*10像素的图像数据
var specialImgData = ctx.getImageData(100, 100, 10, 10);
// 将其变成蓝色
for (var i = 0; i < specialImgData.data.length; i += 4) {
  setGridColor(specialImgData, i, 0, 0, 255, 255);
}
// 再次填充到画布上,其实这样就可以对图片进行各种滤镜处理之类的了
ctx.putImageData(specialImgData, 100, 100);
console.log();

// 设置某个网格的颜色信息
function setGridColor(imgData, i, r, g, b, a) {
  imgData.data[i + 0] = r;
  imgData.data[i + 1] = g;
  imgData.data[i + 2] = b;
  imgData.data[i + 3] = a;
}
// 得到某行某列的特定网格的像素信息
function getGridColor(imageData, row, column) {
  let rIndex = row * (imageData.width * 4) + column * 4 + 0;
  return imageData.data.slice(rIndex, rIndex + 4);
}
// 设置特定某行某列网格的颜色信息
function setSpecialGridColor(imageData, row, column, r, g, b, a) {
  let rIndex = row * (imageData.width * 4) + column * 4 + 0;
  imageData.data[rIndex] = r;
  imageData.data[rIndex + 1] = g;
  imageData.data[rIndex + 2] = b;
  imageData.data[rIndex + 3] = a;
}

像素控制

  • createImageData(width,height) 方法创建新的空白 ImageData 对象。ImageData 的data存了所有的像素信息,是一个数组,每四项对应一个像素的 rgba 信息(a 的话 0 是透明,255 是完全可见),默认是(0,0,0,0)。如数组的 0,1,2,3 对应第一个像素,4,5,6,7 对应第二个像素。 长度是高度 × 宽度 × 4 ,索引值从 0 到长度-1
  • putImageData(imgData,x,y) 方法将图像数据(从指定的 ImageData 对象)放回画布特定位置上
  • getImageData(left, top, width, height),这个方法会返回一个 ImageData 对象,它代表了画布区域的对象数据,此画布的四个角落分别表示为(left, top), (left + width, top), (left, top + height), 以及(left + width, top + height)四个点。这些坐标点被设定为画布坐标空间元素。

上传和下载 canvas 绘制的图片

var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
ctx.fillRect(50, 50, 50, 50);

// 获取图片的base64
var base64 = canvas.toDataURL("image/png", 1);
document.querySelector("#canvasImg").src = base64;

console.log(base64);
// 也可以下载到本地
// openDownloadDialog(base64, `随便起名.png`);

// 可以拿到blob对象,像File那样处理
canvas.toBlob(blobObj => {
  console.log(blobObj);
  // 当然也可以下载到本地
  openDownloadDialog(blobObj, `随便起名blob.png`);
});

// 这里的blob可以是blob文件也可以是base64
function openDownloadDialog(blob, fileName) {
  if (typeof blob === "object" && blob instanceof Blob) {
    blob = URL.createObjectURL(blob); // 创建blob地址
  }
  var aLink = document.createElement("a");
  aLink.href = blob;
  // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,有时候 file:///模式下不会生效
  aLink.download = fileName || "";
  var event;
  if (window.MouseEvent) event = new MouseEvent("click");
  // 移动端
  else {
    event = document.createEvent("MouseEvents");
    event.initMouseEvent( "click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null );
  }
  aLink.dispatchEvent(event);
}

保存和下载

  • canvas.toDataURL('image/png'),默认设定。创建一个PNG图片。
  • canvas.toDataURL('image/jpeg', quality),创建一个JPG图片。可以有选择地提供从0到1的品质量,1表示最好品质,0基本不被辨析但有比较小的文件大小。
  • canvas.toBlob(callback, type, encoderOptions)创建了一个在画布中的代表图片的Blob对像。

可能用到的canvas代码片段

mdn写的很详细,不粘贴了。

  • 在画布中获取特定颜色的像素数量
  • 在画布中获取某一个像素的颜色
  • 链式调用方法
  • 将canvas图片保存到文件中
  • 将一个远程页面加载到canvas元素上
  • 将图像文件转换为base64字符串

引用

MDN 的 Canvas 教程
MDN 的canvas可能用到的代码片段 w3school 的 canvas 教程