H5 - Canvas 技术详解

文章目录

      • 1. 介绍
      • 2. 基本用法
        • 2.1 替代内容(不支持时显示)
        • 2.2 获取 2D 渲染上下文:getContext()
        • 2.3 代码模板
      • 3. 绘制形状
        • 3.1 画布栅格 及 坐标空间
        • 3.2 唯一可以原生绘制的图形:矩形
        • 3.3 绘制路径
        • 3.4 各种 子路径类型 的绘制方法
          • 3.4.1 使用 二次贝塞尔曲线,画对话气泡:quadraticCurveTo()
          • 3.4.2 使用 三次贝塞尔曲线,画心形:bezierCurveTo()
          • 3.4.3 子路径 综合使用,画 经典游戏吃豆人 的静态界面
        • 3.5 缓存路径:Path2D 对象
          • 3.5.1 Path2D 对象的使用示例
          • 3.5.2 使用 SVG paths 作为 Path2D对象 的参数,进行路径缓存
      • 4. 添加颜色 和 样式
        • 4.1 色彩属性
          • 4.1.1 画出渐变色板:fillStyle 属性
          • 4.1.2 画出渐变圆形组:strokeStyle 属性
        • 4.2 透明度:globalAlpha 属性
        • 4.3 线型属性(线宽,末端样式,节点样式,虚线设置)
        • 4.4 渐变:canvasGradient 对象
          • 4.4.1 线性渐变 示例:createLinearGradient()
          • 4.4.1 径向渐变 示例:createRadialGradient()
        • 4.5 背景图案:createPattern()
        • 4.6 阴影属性
      • 5. 绘制文本
        • 5.1 基本使用
        • 5.2 文本样式 属性
        • 5.3 测量 文本宽度:measureText()
      • 6. 图像操作
        • 6.1 图片来源
        • 6.2 放置 / 缩放 / 切片 图片:drawImage()
          • 6.2.1 放置:线性图的背景
          • 6.2.2 缩放:图片重复平铺
          • 6.2.3 切片:制作相框
          • 6.2.4 放置:制作画廊
      • 7. 变形:画布的移动,缩放 和 旋转
        • 7.1 画布状态的保存和恢复:save(),restore()
        • 7.2 画布的移动:translate()
        • 7.3 画布的缩放:scale()
        • 7.4 画布的旋转:rotate()
        • 7.5 综合变形:transform()
        • 7.6 变形总结
      • 8. 合成与裁剪
        • 8.1 合成属性:globalCompositeOperation
        • 8.2 裁剪:clip()
          • 8.2.1 渲染星星的方法
      • 9. 基本动画
        • 9.1 动画的基本步骤:画出一帧
        • 9.2 操控动画的每一帧
          • 9.2.1 requestAnimationFrame() 示例 1:太阳系模拟动画
          • 9.2.2 requestAnimationFrame() 示例 2:动画时钟
          • 9.2.3 setInterval() 示例:循环全景照片
        • 9.3 基础动画绘制 小结
      • 10. 高级动画 案例
        • 10.1 小球运动 案例
        • 10.2 打砖块游戏(待完成)
      • 11. 像素操作:
        • 11.1 像素的读取和写入
          • 11.1.1 ImageDate 对象的 创建,读取,写入
          • 11.1.2 ImageDate 对象的属性
          • 11.1.3 像素读取 案例:颜色读取器,getImageData()
            • 11.1.3.1 获取 图像 的 像素数据时,受跨域影响,解决方案
          • 11.1.4 像素写入案例:颜色反相 和 图片灰度,putImageData()
        • 11.2 缩放 和 反锯齿:imageSmoothingEnabled 属性
          • 11.2.1 缩放 和 反锯齿 综合示例
        • 11.3 保存图片
          • 11.3.1 直接生成 图片URL:toDataURL()
          • 11.3.1 通过图片对象 Blob 生成 图片URL
      • 12. canvas 的优化
        • 12.1 优化建议
      • 13 其他
        • 13.1 位图(Canvas) 和 矢量图(SVG) 的区别
        • 13.2 其他图形相关的 API
        • 13.3 相关应用教程

1. 介绍

Canvas 技术涉及到 JS 知识,请先参阅 《JavaScript 技术详解》 博文。

  • 参考文档:Mozilla 提供的: Canvas 教程(中文)
  • 介绍: 是一个可以使用脚本 (通常为 JavaScript) 来绘制图形的 HTML 标签。
  • 应用:它可以用于 绘制图表制作图片构图制作动画
  • 注意:
    • 的默认大小为 300像素×150像素(宽×高,像素的单位是px),可以使用 高度 和 宽度 属性来自定义Canvas 的尺寸。
    • 尽量不要使用 css 去控制图像的大小,因为如果 css 定义的大小和 画布的初始大小不一致时,图像会去适应画布大小,导致图像扭曲。直接设置画布 width 和 height 属性。
    • 当开始时没有为 canvas 规定 样式规则,其将会完全透明。

2. 基本用法

2.1 替代内容(不支持时显示)

  • 看起来和 元素很相像,唯一的不同就是它并没有 src 和 alt 属性。
  • 由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者 文本浏览器 不支持 HTML 标签 ,在这些浏览器上,应该总是能展示 替代内容
  • 可以提供对 canvas内容的 文字描述静态图片,如:

<canvas id="stockGraph" width="150" height="150">
	current stock price: $3.15 +0.15
canvas>


<canvas id="clock" width="150" height="150">
	<img src="images/clock.png" width="150" height="150" alt=""/>
canvas>

2.2 获取 2D 渲染上下文:getContext()

标签创造了一个固定大小的 画布,它公开了一个或多个 渲染上下文,其可以用来 绘制 和 处理 要展示的内容。
我们将会将注意力放在 2D 渲染上下文,即 CanvasRenderingContext2D 对象。

获取 2D 渲染上下文 对象:

// 获取画布元素
var canvas = document.getElementById('tutorial');
// 检查浏览器对 canvas 的支持性
if (canvas.getContext){
	// 并获取 2D 渲染上下文 对象
	var ctx = canvas.getContext('2d');
	// 绘制代码...
} else {
	// 不支持 Canvas
}

2.3 代码模板




<html>
	<head>
		<script type="application/javascript">
			function draw() {
				var canvas = document.getElementById('canvas');
				if (canvas.getContext) {
					var ctx = canvas.getContext('2d');
	
					ctx.fillStyle = 'rgb(200,0,0)';
					ctx.fillRect (10, 10, 55, 50);
	
					// rgba 最后一个参数表示 alpha 透明度
					ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
					ctx.fillRect (30, 30, 55, 50);
				}
		  	}
		script>
	head>

	<body onload="draw()">
		<canvas id="canvas" width="150" height="150">canvas>
	body>
html>

3. 绘制形状

3.1 画布栅格 及 坐标空间

  • 画布栅格:栅格的起点为左上角,坐标为(0, 0)。
  • 坐标空间:所有 元素的位置 都相对于 原点定位。

3.2 唯一可以原生绘制的图形:矩形

  • 不同于 SVG,HTML中的元素 Canvas 只支持 1 种 原生图形绘制:矩形。
  • 其他的图形 的绘制都至少需要生成 1 条路径
  • 不过,我们拥有众多 路径生成 的方法,让复杂图形的绘制成为了可能。

Canvas 提供了 3 种方法 绘制矩形:

  • fillRect(x, y, width, height):绘制一个 填充 的矩形。
  • strokeRect(x, y, width, height):绘制一个矩形的 边框
  • clearRect(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);
	}
}

3.3 绘制路径

图形的基本元素是路径。使用 路径 绘制图形 的步骤:

  1. 创建路径 起始点
  2. 使用 画图命令 去画出路径。
  3. 把路径 封闭
  4. 一旦路径生成,你就能通过 描边填充 路径区域 来渲染图形。

绘制路径 所要用到的函数:

  1. 生成路径:
    a. beginPath():新建一条路径,生成之后,图形绘制命令 被指向到路径上 生成路径。
    每次这个方法调用之后,画布会清空重置
    第一条 路径构造 命令通常 是 moveTo()。
    b. moveTo(x, y) 非必须:将笔触(就是 新的起点 的意思)移动到指定的坐标 (x,y) 上。简单点理解,就是换一个地方开始绘制,断开之前连续的路径。
    c. closePath() 非必须:闭合路径之后,图形绘制命令 又重新指向到 上下文中。
    使用场景:仅仅在使用线条绘制 stroke() ,并且终点不在起点时,才需要 closePath()。
  2. 中间是各种 子路径,包括 线、弧形 等等,下面一节会介绍具体介绍 各种 子路径 函数
  3. 路径构建完毕后,开始绘制图形,包括 线条,和填充 2 种方式:
    a. stroke() 不会自动闭合路径:通过 线条 来绘制 图形轮廓。
    b. 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();
	}
}

// 绘制一个描边三角形
function draw() {
	var canvas = document.getElementById('canvas');
	if (canvas.getContext) {
 		var ctx = canvas.getContext('2d');

		// 构建路径
 		ctx.beginPath();
		ctx.moveTo(125,125);
		ctx.lineTo(125,45);
		ctx.lineTo(45,125);
		ctx.closePath();
		// 绘制图形,线条
		ctx.stroke();
	}
}

// 绘制一个笑脸
function draw() {
	var canvas = document.getElementById('canvas');
	if (canvas.getContext){
		var ctx = canvas.getContext('2d');
		
		// 构建路径
  		ctx.beginPath();
  		ctx.arc(75,75,50,0,Math.PI*2,true);
  		// 这里坐标随意,主要断开之间的路径,开始画嘴巴
  		ctx.moveTo(110,75);
 		ctx.arc(75,75,35,0,Math.PI,false);
 		// 左眼
  		ctx.moveTo(65,65);
  		ctx.arc(60,65,5,0,Math.PI*2,true);
  		// 右眼
  		ctx.moveTo(95,65);
  		ctx.arc(90,65,5,0,Math.PI*2,true);
  		// 绘制图形,线条
  		ctx.stroke();
	}
}

3.4 各种 子路径类型 的绘制方法

  • lineTo(x, y):构建 直线 路径。绘制一条从当前位置到指定(x, y)位置的直线。
  • arc(x, y, radius, startAngle, endAngle, anticlockwise):构建 圆弧 或 圆 路径。弧度起点为 x轴正方向
    • x,y:圆心坐标。
    • radius:圆的半径。
    • startAngle, endAngle:弧度的起点和终点。1PI 弧度 对应 180度
    • anticlockwise:默认为 false,表示顺时针方向开始画。
  • rect(x, y, width, height):构建矩形路径。区别于 fillRect 等三个直接矩形绘制方法,这只是画出路径
    • x, y:左上角坐标。
    • width, height:矩形的宽和高。
  • quadraticCurveTo(cp1x, cp1y, x, y):绘制 二次贝塞尔曲线
  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):绘制 三次贝塞尔曲线
    • cp1x, cp1y :红色控制点一
    • cp2x, cp2y:红色控制点二
    • x, y:蓝色结束点
    • 上一个路径 的终点:为蓝色起始点
      H5 - Canvas 技术详解_第1张图片
3.4.1 使用 二次贝塞尔曲线,画对话气泡:quadraticCurveTo()
function draw() {
	var canvas = document.getElementById('canvas');
	if (canvas.getContext) {
		var ctx = canvas.getContext('2d');
		
		// 构建路径
		ctx.beginPath();
		ctx.moveTo(75,25);
		ctx.quadraticCurveTo(25,25,25,62.5);
		ctx.quadraticCurveTo(25,100,50,100);
		ctx.quadraticCurveTo(50,120,30,125);
		ctx.quadraticCurveTo(60,120,65,100);
		ctx.quadraticCurveTo(125,100,125,62.5);
		ctx.quadraticCurveTo(125,25,75,25);
		// 绘制图形,线条
		ctx.stroke();
	}
}

H5 - Canvas 技术详解_第2张图片

3.4.2 使用 三次贝塞尔曲线,画心形:bezierCurveTo()

function draw() {
	var canvas = document.getElementById('canvas');
	if (canvas.getContext){
		var ctx = canvas.getContext('2d');
		
		// 构建路径
		ctx.beginPath();
		ctx.moveTo(75,40);
		ctx.bezierCurveTo(75,37,70,25,50,25);
		ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
		ctx.bezierCurveTo(20,80,40,102,75,120);
		ctx.bezierCurveTo(110,102,130,80,130,62.5);
		ctx.bezierCurveTo(130,62.5,130,25,100,25);
		ctx.bezierCurveTo(85,25,75,37,75,40);
		// 绘制图形,填充
		ctx.fill();
	}
}

H5 - Canvas 技术详解_第3张图片

3.4.3 子路径 综合使用,画 经典游戏吃豆人 的静态界面
function draw() {
	var canvas = document.getElementById('canvas');
	if (canvas.getContext){
		var ctx = canvas.getContext('2d');
		
		// 使用封装好的 圆角矩形绘制函数,记得提前设置好画布的大小。
		// 构建 游戏边界 路径
		roundedRect(ctx,19,19,170,152,9);
		// 构建 4个障碍物 路径
		roundedRect(ctx,53,53,49,33,10);
		roundedRect(ctx,53,119,49,16,6);
		roundedRect(ctx,135,53,49,33,10);
		roundedRect(ctx,135,119,25,49,10);
		
		// 构建 吃豆人 路径
		ctx.beginPath();
		ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,false);
		ctx.lineTo(31,37);
		// 绘制吃豆人,填充
		ctx.fill();
		
		// 使用原生绘制 矩形的豆豆
		for(var i = 0; i < 8; i++) {
			ctx.fillRect(51 + i * 16, 35, 4, 4);
		}
		for(var i = 0; i < 6; i++) {
			ctx.fillRect(115, 51 + i * 16, 4, 4);
		}
		for(var i = 0; i < 8; i++) {
			ctx.fillRect(51 + i * 16, 99, 4, 4);
		}
		
		// 构建 怪物身体 路径
		ctx.beginPath();
		ctx.moveTo(83, 116);
		// 左侧身子
		ctx.lineTo(83, 102);
		// 使用三次贝塞尔曲线,构建 头部
		ctx.bezierCurveTo(83,94,89,88,97,88);
		ctx.bezierCurveTo(105,88,111,94,111,102);
		// 右侧身子
		ctx.lineTo(111,116);
		// 下身的锯齿,共6条直线
		ctx.lineTo(106.333,111.333);
		ctx.lineTo(101.666,116);
		ctx.lineTo(97,111.333);
		ctx.lineTo(92.333,116);
		ctx.lineTo(87.666,111.333);
		ctx.lineTo(83,116);
		// 绘制 怪物身体,填充
		ctx.fill();
		
		// 构建 怪物眼睛轮廓 路径
		ctx.fillStyle = "white";
		ctx.beginPath();
		// 左眼,从左上角开始
		ctx.moveTo(91,96);
		ctx.bezierCurveTo(88,96,87,99,87,101);
		ctx.bezierCurveTo(87,103,88,106,91,106);
		ctx.bezierCurveTo(94,106,95,103,95,101);
		ctx.bezierCurveTo(95,99,94,96,91,96);
		// 右眼,从右上角开始
		ctx.moveTo(103,96);
		ctx.bezierCurveTo(100,96,99,99,99,101);
		ctx.bezierCurveTo(99,103,100,106,103,106);
		ctx.bezierCurveTo(106,106,107,103,107,101);
		ctx.bezierCurveTo(107,99,106,96,103,96);
		// 绘制 怪物眼睛轮廓,填充
		ctx.fill();
		
		// 构建 怪物黑色眼珠 路径
		ctx.fillStyle = "black";
		ctx.beginPath();
		// 左眼珠
		ctx.arc(89, 102, 2, 0, Math.PI*2, true);
		ctx.fill();
		// 右眼珠
		ctx.beginPath();
		ctx.arc(101, 102, 2, 0, Math.PI*2, true);
		ctx.fill();
	}
}
	
// 封装的一个用于绘制 圆角矩形 的函数
function roundedRect(ctx, x, y, width, height, radius) {
	// 构建路径
	ctx.beginPath();
	ctx.moveTo(x, y + radius);
	// 左边框 路径
	ctx.lineTo(x, y + height - radius);
	// 利用 二次贝塞尔曲线,构建 左下角的 圆弧 路径
	ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
	// 下边框
	ctx.lineTo(x + width - radius, y + height);
	// 右下角的 圆弧
	ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
	// 右边框
	ctx.lineTo(x + width, y + radius);
	// 右上角的 圆弧
	ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
	// 上边框
	ctx.lineTo(x + radius, y);
	// 左上角的 圆弧
	ctx.quadraticCurveTo(x, y, x, y + radius);
	// 绘制 圆角矩形,线条
	ctx.stroke();
}

H5 - Canvas 技术详解_第4张图片

3.5 缓存路径:Path2D 对象

  • Path2D 对象 是用来 缓存 或 记录 绘画命令,达到 一次构建,多次使用 的效果。
  • 所有的路径方法,如 moveTo, rect, arcquadraticCurveTo 等,都可以在 Path2D 对象中使用。
  • 它的出现是为了 简化代码提高性能。Path2D 对象已可以在较新版本的浏览器中使用。
  • 构建 Path2D 对象 的方法:
    new Path2D():返回一个新初始化的 Path2D 对象,空的。
    new Path2D(path):可以添加路径 path。
    new Path2D(d):可以添加 SVG path 数据的字符串,来初始化 canvas 上的路径。
  • 方法:
    Path2D.addPath(path)​:添加了一条路径到当前路径,不常用。
3.5.1 Path2D 对象的使用示例
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);
  }
}
3.5.2 使用 SVG paths 作为 Path2D对象 的参数,进行路径缓存
function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
    var ctx = canvas.getContext('2d');
    
	let p = new Path2D('M10 10 h 80 v 80 h -80 Z');
	// path 解释:
	// M10 10:起点
	// h 80:左移 80 单位
	// v 80:下移 80 单位
	// h -80:右移 80 单位
	// Z:回到起点

	// 使用 缓存到的 SVG paths,绘制图形
	ctx.fill(p);
  }
}

4. 添加颜色 和 样式

4.1 色彩属性

  • 色彩属性: fillStyle, strokeStyle,分别 设置图形的 填充 颜色,和 轮廓 颜色。
  • 属性值 可以是表示 CSS 颜色值的字符串"orange""#FFA500""rgb(255,165,0)""rgba(255,165,0,1)"
  • 也可以是 渐变对象 canvasGradient,背景图案对象 pattern
  • 默认情况下,线条 和 填充颜色都是 黑色#000000
  • CSS3 颜色值标准
4.1.1 画出渐变色板:fillStyle 属性
function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	var ctx = canvas..getContext('2d');
	  for(var i=0;i<6;i++) {
	    for(var j=0;j<6;j++) {
	      ctx.fillStyle = 'rgb(' + Math.floor(255 - 42.5 * i) + ',' + 
	                       Math.floor(255 - 42.5 * j) + ',0)';
	      ctx.fillRect(j*25, i*25, 25, 25);
	    }
	  }
	}
}

H5 - Canvas 技术详解_第5张图片

4.1.2 画出渐变圆形组:strokeStyle 属性
function draw() {
    var canvas = document.getElementById('canvas');
	  if (canvas.getContext){
	  	var ctx = canvas..getContext('2d');
	    for (var i=0;i<6;i++) {
	      for (var j=0;j<6;j++) {
	        ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' + 
	                         Math.floor(255-42.5*j) + ')';
	        ctx.beginPath();
	        ctx.arc(12.5 + j * 25, 12.5 + i * 25, 10, 0, Math.PI*2, true);
	        ctx.stroke();
	      }
	    }
	}
}

H5 - Canvas 技术详解_第6张图片

4.2 透明度:globalAlpha 属性

  • 透明度设置 可以 通过设置 globalAlpha 属性 或 使用 透明颜色 rgba 作为 轮廓 或 填充 的颜色样式。
  • 有效的值范围都是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。
  • globalAlpha 属性 在整个 2D 渲染上下文 中生效,而 rgba 属性 当 颜色样式重置后就失效了。

示例:

ctx.globalAlpha = 0.5;

ctx.strokeStyle = "rgba(255,0,0, 0.5)";
ctx.fillStyle = "rgba(255,0,0, 0.5)";

4.3 线型属性(线宽,末端样式,节点样式,虚线设置)

通过一系列 属性方法 来设置 线的样式

线型相关 属性 和 方法:

属性 意义 属性值 示例
lineWidth 设置线条宽度。
线宽是指给定路径的中心到两边的粗细。换句话说就是在路径的两边各绘制 线宽 的一半。
需要注意的是,线宽两旁,或 上下 的边界最好要与画布上的像素边界重合,不要在中间,否则会出现 半渲染的像素点,得不到精确的线条。
必须为正数。
默认值是1.0
ctx.lineWidth = 1.5
lineCap 设置线条末端样式。
只有路径的起点和终点受此影响:
1. 如果一个路径是通过closePath()来封闭的,它是没有起点和终点的。
2. 相反的情况下,路径上的所有端点都与上一个点相连,下一段路径会使用当前的 lineJoin 设置, lineCap 影响的是整个路径的两端。
1. butt:默认值,末端与边界齐平。
2. round:末端突出 半径为线宽的一半的 半圆。
3. square:末端突出 长度为线宽一半的 等宽长方形。
ctx.lineCap = "square"
lineJoin 设定线条与线条间接合处的样式 1. miter:默认值,线段会在连接处外侧延伸直至交于一点,延伸效果受到 miterLimit 属性的长度制约。
2. round:边角处会被磨圆,圆的半径等于线宽。
3. bevel:边角处会被磨平。
ctx.lineJoin = "round"
miterLimit 限制当两条线相交时交接处最大长度。

所谓交接处长度(斜接长度)是指 线条交接处 内角顶点外角顶点 的长度。如果交点距离大于此值,连接效果会变成了 bevel

注意:两线段相交时,线段的外侧边缘会延伸交汇于一点上。线段直接夹角比较大的,交点不会太远,但当夹角减少时,交点距离会呈指数级增大。
正数。 ctx.miterLimit = "3"
lineDashOffset 设置虚线样式的起始偏移量 一般设置为 0
setLineDash(segments) 设置当前虚线样式。 接受一个数组,来指定线段与间隙的交替。 ctx.lineDashOffset = 0;

ctx.setLineDash([4, 2]);
ctx.strokeRect(10,10, 100, 100);
getLineDash() 返回一个包含当前 虚线 样式,长度为非负偶数的数组。如果数组为 奇数,数组会被复制并重复一遍。
作用就是,获取 setLineDash(segments) 的数组。
ctx.getLineDash()

4.4 渐变:canvasGradient 对象

可以使用 线性径向canvasGradient 对象,赋值给 颜色属性,来实现 渐变 描边 或 填充。

使用方法:

  1. 需要通过 ctx 创建 canvasGradient 对象
  • var gradient = ctx.createLinearGradient(x1, y1, x2, y2)线性渐变,接受 4 个参数,表示渐变的 起点终点
  • var gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2)径向渐变,接受 6 个参数,表示渐变的 起点圆终点圆
  • 这 2 个对象都可以实现渐变,只是 渐变效果 不同而已。
  1. 设置 canvasGradient 对象 的 渐变终止位置 和 终止颜色
  • gradient.addColorStop(position, color):接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示 渐变终止 的位置。color 表示渐变终止的 颜色。
  1. 把 canvasGradient 对象赋值给 fillStyle 或 strokeStyle 属性,进行图形绘制
  • ctx.fillStyle = gradient;
  • ctx.strokeStyle = gradient;
  • ctx.fillRect(10,10,130,130);
  • ctx.strokeRect(50,50,50,50);
4.4.1 线性渐变 示例:createLinearGradient()
function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');

	  // 创建 gradients 线性渐变对象
	  var lingrad1 = ctx.createLinearGradient(0,0,0,150);
	  // 添加渐变属性
	  lingrad.addColorStop(0, '#00ABEB');
	  lingrad.addColorStop(0.5, '#fff');
	  lingrad.addColorStop(0.5, '#26C000');
	  lingrad.addColorStop(1, '#fff');
	
	  // 创建 gradients 线性渐变对象
	  var lingrad2 = ctx.createLinearGradient(0,50,0,95);
	  // 添加渐变属性
	  lingrad2.addColorStop(0.5, '#000');
	  lingrad2.addColorStop(1, 'rgba(0,0,0,0)');
	
	  // 设置 样式属性
	  ctx.fillStyle = lingrad1;
	  ctx.strokeStyle = lingrad2;
	  
	  // 绘制图形
	  ctx.fillRect(10,10,130,130);
	  ctx.strokeRect(50,50,50,50);
	}
}

H5 - Canvas 技术详解_第7张图片

4.4.1 径向渐变 示例:createRadialGradient()

这里说一下,简单的径向渐变就是,只有一个中心点,简单地由中心点向外围的圆形扩张。
下面的比这要复杂一点,定义了 4 个不同的径向渐变 的 重叠矩形,并设置了 渐变外的颜色 为 透明色。

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');
  	  
	  // 创建 gradients 径向渐变对象
	  // 绿色
	  var radgrad1 = ctx.createRadialGradient(45,45,10,52,50,30);
	  // 添加渐变属性
	  radgrad.addColorStop(0, '#A7D30C');
	  radgrad.addColorStop(0.9, '#019F62');
	  // 最后的颜色代表了,渐变结束后的 其他部分的 绘制颜色
	  // 最后一部分设置为透明色,是为了让 4 个矩形 渐变外的颜色透明,使得重叠可见
	  radgrad.addColorStop(1, 'rgba(1,159,98,0)');
	  
	  // 红色
	  var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50);
	  radgrad2.addColorStop(0, '#FF5F98');
	  radgrad2.addColorStop(0.75, '#FF0188');
	  radgrad2.addColorStop(1, 'rgba(255,1,136,0)');
	
	  // 蓝色
	  var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40);
	  radgrad3.addColorStop(0, '#00C9FF');
	  radgrad3.addColorStop(0.8, '#00B5E2');
	  radgrad3.addColorStop(1, 'rgba(0,201,255,0)');
	  
	  // 黄色
	  var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90);
	  radgrad4.addColorStop(0, '#F4F201');
	  radgrad4.addColorStop(0.8, '#E4C700');
	  radgrad4.addColorStop(1, 'rgba(228,199,0,0)');
	  
	  // 绘制4个矩形,这4个矩形坐标和半径都相同,全部重叠,但是最后一部分都设置了透明色,所以看得见
	  ctx.fillStyle = radgrad4;
	  ctx.fillRect(0,0,150,150);
	  
	  ctx.fillStyle = radgrad3;
	  ctx.fillRect(0,0,150,150);
	  
	  ctx.fillStyle = radgrad2;
	  ctx.fillRect(0,0,150,150);
	  
	  ctx.fillStyle = radgrad1;
	  ctx.fillRect(0,0,150,150);
	}
}

H5 - Canvas 技术详解_第8张图片

4.5 背景图案:createPattern()

可以使用 pattern 对象,赋值给 颜色属性,来实现 图案背景 设置,并可以控制 循环方式。

使用方法:

  1. 需要通过 ctx 创建 pattern 对象:
  • ctx.createPattern(image, type):接受两个参数。
    • image 可以是一个 Image 对象的引用,或者另一个 Canvas 对象( 但在 Firefox 1.5 (Gecko 1.8) 中是无效的)。
    • type 必须是下面的字符串值之一:repeatrepeat-xrepeat-yno-repeat
  1. 然后赋值给 颜色属性 即可。

注意:

必须确保 image 对象在使用前已经 加载 完毕

示例:

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');
  	  
	  // 创建新 image 对象,用作图案
	  var img = new Image();
	  img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png';
	 
	 // 确保 image 对象 加载完成
	  img.onload = function() {
	    // 创建图案样式 pattern 对象
	    var ptrn = ctx.createPattern(img, 'repeat');
	    // 添加到样式属性
	    ctx.fillStyle = ptrn;
	
	    // 绘制图形,填充
	    ctx.fillRect(0, 0, 150, 150);
	  }
	}
}

原始图片:
原始图片
图片重复填充后:
H5 - Canvas 技术详解_第9张图片

4.6 阴影属性

阴影属性 意义
shadowOffsetXshadowOffsetY 设定阴影在 X 和 Y 轴的延伸距离。
负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0。
shadowBlur 设定阴影的模糊程度。
其数值并不跟像素数量挂钩,默认为 0。
shadowColor 设定阴影颜色效果。
默认是全透明的黑色。

示例:这里以 绘制 文本的阴影 为例,文本的绘制 下一节会讲到

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = 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);
	}
}

以文本为例,效果:
阴影属性 文本示例

5. 绘制文本

注意:在 Geoko(Firefox,Firefox OS 及 基于 Mozilla 的应用的渲染引擎)中,曾有一些版本较早的 API 实现了在 Canvas上对 文本 作画的功能,但它们现在已不再使用。

5.1 基本使用

  • 绘制文本有 2 种方法:
    • fillText(text, x, y , [maxWidth]):在指定的 (x,y) 位置 填充 指定的文本(就是实体字)。绘制的最大宽度可选。
    • strokeText(text, x, y , [maxWidth]):在指定的 (x,y) 位置 描边 文本边框(就是空心字)。绘制的最大宽度可选。

示例:

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');
  	  
	  // 设置字体样式
	  ctx.font = "48px serif";
	  // 绘制文本
	  ctx.fillText("Hello world", 10, 50);
	  ctx.strokeText("Hello world", 10, 80);
	}
}

5.2 文本样式 属性

属性 意义 属性值 示例
font 设置字体 属性值为 字符串,使用和 CSS font 属性相同的语法。
默认的字体是 10px sans-serif
textAlign 设置文本对齐方式 默认值是 start
其他可能的值为
start:文本对齐界线开始的地方 (左对齐指本地从左向右,右对齐指本地从右向左)
end:文本对齐界线结束的地方 (左对齐指本地从左向右,右对齐指本地从右向左)
left:左对齐
right:右对齐
center:居中对齐
textBaseline 设置基线对齐方式 默认值为 alphabetic
其他可能的值为 (水平位置从上到下):
top:在文本块的顶部
hanging:是悬挂基线
middle:在文本块的中间
alphabetic:是标准的字母基线
ideographic:是表意字基线
bottom:在文本块的底部
direction 设置文本方向 默认值是 inherit
其他可能的值为:
inherit:根据情况继承 元素或者 Document。
ltr:从左向右
rtl: 从右向左

关于基线对齐的说明 图解:
H5 - Canvas 技术详解_第10张图片

5.3 测量 文本宽度:measureText()

  • 使用方法:
    measureText(text):传入文本参数,返回一个 TextMetrics 对象,该对象持有文本的 宽度所在像素 等属性。

测量文本宽度示例:

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

6. 图像操作

  • Canvas 更有意思的一项特性就是 图像操作 能力。
  • 用途:
  1. 作为图形的背景 ,4.5 背景图案,pattern 对象 小节中有说明。
  2. 制作 动态的图像

6.1 图片来源

图片来源 使用方式 示例
JPEG,PNG,GIF 等 通过 挂在 HTMLImageElement 对象上 使用。

具体操作:
HTMLImageElement 对象 可以通过 Image() 函数构造出来,或者是任何的 元素。
1. 获取相同页面内的图片 的 2 种方法
a. document.images :返回当前文档中所有 image 元素的集合,遍历集合,通过相关属性 src 等找出需要的图片。
var ilist = document.images;
for(var i = 0; i < ilist.length; i++) {
if(ilist[i].src == "banner.gif") {
// 发现了banner图片
}
}

b. document.getElementsByTagName()
或 document.getElementById()


2. 由零开始创建图像
我们可以用脚本创建一个新的 HTMLImageElement 对象。
可以使用很方便的 Image() 构造函数:
var img = new Image(); // 创建一个元素
img.src = 'myImage.png'; // 设置图片源地址
img.onload = function(){
// 执行drawImage语句
}


3. 使用其它域名下的图片的 像素数据 时:
需要先给图像对象设置 crossorigin="anonymous" 属性,然后服务器也要设置 access-control-allow-origin 设置为 * 或 选定的域。
如果只是展示使用图片则不需要设置。


4. 通过 data: url 方式嵌入图像
Data urls 允许用一串 Base64 编码的字符串的方式来定义一个图片。
img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn
/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';
同一个页面中 其他 canvas元素 生成的 图片 通过 HTMLCanvasElement 对象使用。

具体操作:
直接使用另一个 元素作为图片源。
1. 使用其它 canvas 元素
和引用页面内的图片类似地,用 document.getElementsByTagNamedocument.getElementById 方法来获取其它 canvas 元素。但你引入的应该是已经准备好的 canvas。

一个常用的应用就是将第二个canvas作为另一个大的 canvas 的缩略图。
从 视频 中抓取 当前帧 作为一个图像 通过 HTMLVideoElement 对象使用。

具体操作:
直接用一个HTML的 元素作为你的图片源。
1. 使用视频帧
使用 中的视频帧(即便视频是不可见的)。
例如,如果你有一个ID为 “myvideo” 的 元素:
document.getElementById('myvideo');
它将为这个视频返回HTMLVideoElement 对象,它可以作为我们的 Canvas 图片源。
高性能的位图,以低延迟地绘制 通过 ImageBitmap 对象使用。

具体操作:
ImageBitmap 对象可以从上述的所有源以及其它几种源的 对象 中生成。

6.2 放置 / 缩放 / 切片 图片:drawImage()

  • drawImage() 方法的 3 种形态:
    • drawImage(image, x, y):放置图片。

      • image:image 或者 canvas 对象。
      • x 和 y: 是其在目标 canvas 里的起始坐标。
    • drawImage(image, x, y, width, height):缩放图片。

      • width 和 height:控制当向 canvas 画入时,应该缩放的大小。
    • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight):切片图片。

      • 前4个参数:定义 图像源 的切片位置和大小。
      • 后4个参数:定义 切片的 目标显示位置和大小。

    注意点:

    • 禁止图像的缩放行为:Gecko 1.9.2 引入了 mozImageSmoothingEnabled 属性,默认是 true 。
      值为 false 时,图像不会平滑地缩放。
      ctx.mozImageSmoothingEnabled = false; // 禁止缩放
    • 切片 是个做图像合成的强大工具。假设有一张包含了所有元素的图像,那么你可以用这个方法来合成一个完整图像。
6.2.1 放置:线性图的背景
// 线性图的背景,除了 折线 外,其余的是背景
function draw() {
    var canvas = document.getElementById('canvas');
	if (canvas.getContext){
	  	var ctx = canvas..getContext('2d');
	  	
	    var img = new Image();
	    img.src = 'images/backdrop.png';
	
	    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();
	    };
	  }
  }

H5 - Canvas 技术详解_第11张图片

6.2.2 缩放:图片重复平铺
// 缩放图片重复平铺
function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');
  	  
	  var img = new Image();
	  img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
	  
	  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); // 缩放图片
	      }
	    }
	  };
	}
}

H5 - Canvas 技术详解_第12张图片

6.2.3 切片:制作相框
  • 相框,图像 和 相框 放在 HTML 中,赋予 id,使用 CSS 隐藏。
  • 然后通过获取 对象作为 drawImage 的 图像源。
  • 最后进行 切片,绘制 等。
function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');

	  // 切片 相片
	  ctx.drawImage(document.getElementById('source'),
	                33,71,104,124,21,20,87,104);
	  // 绘制 放置相片框
	  ctx.drawImage(document.getElementById('frame'),0,0);
	}
}

H5 - Canvas 技术详解_第13张图片

6.2.4 放置:制作画廊

HTML 中有 隐藏的,排列好的 原画,以及 隐藏的 相框
下面的案例只是 放置画,然后 放置相框

function draw() {
  // 加载所有 image 对象
  for (i=0;i<document.images.length;i++){
    // 循环所有 图片 对象,除去相框
    if (document.images[i].getAttribute('id')!='frame'){
      // 创建画布对象
      var canvas = document.createElement('canvas');
      canvas.setAttribute('width',132);
      canvas.setAttribute('height',150);

      // 将画布插入到 隐藏的图像 节点之前
	  document.images[i].parentNode.insertBefore(canvas, document.images[i]);

	  if (canvas.getContext){
	  	  var ctx = canvas..getContext('2d');
	 	  // 放置 相片
	      ctx.drawImage(document.images[i],15,20);
	 	  // 放置相片框
	      ctx.drawImage(document.getElementById('frame'),0,0);
	    }
    }
  }
}

H5 - Canvas 技术详解_第14张图片

7. 变形:画布的移动,缩放 和 旋转

变形是一种更强大的方法,可以将 画布 的原点 移动 到另一点,对网格进行 缩放旋转

7.1 画布状态的保存和恢复:save(),restore()

在了解变形之前,先介绍 2个 在你开始绘制 复杂图形 时必不可少的方法:

  • save():暂时保存画布的所有状态,可以 restore 恢复(保存状态之后的操作都会被忽略)。
  • restore():恢复到 上一次 save的 canvas 的状态。

Canvas 的状态:当前画面 应用的所有 样式属性变形 的一个快照。
一个绘画状态包括

  • 样式属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 等的值
  • 当前应用的变形:translate()scale()rotate()transform()
  • 当前的 裁切 路径(在下一节会介绍)

保存和恢复的 流程

  • Canvas 状态存储在栈中,每当 save() 方法被调用后,当前的状态就被推送到栈中保存。你可以调用任意多次 save 方法。
  • 每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

示例:save() 和 restore() 的应用

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  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);   // 使用加载的配置绘制一个矩形
	}
}

H5 - Canvas 技术详解_第15张图片

注意:在做变形之前先保存状态是一个良好的习惯
如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。

7.2 画布的移动:translate()

translate(x, y):它用来移动 画布 和它的 原点 到一个不同的位置。

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

示例:绘制 9个 颜色不同的方块

function draw() {
	var canvas = document.getElementById('canvas');
   	if (canvas.getContext){
 	  var ctx = canvas..getContext('2d');
 	  
	  for (var i = 0; i < 3; i++) {
	    for (var j = 0; j < 3; j++) {
	      ctx.save();
	      ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
	      ctx.translate(10 + j * 50, 10 + i * 50);
	      ctx.fillRect(0, 0, 25, 25);
	      
	      // 必须 保存和恢复 canvas 的状态,否则 translate 会一直累加,最后超出画布范围
	      ctx.restore();
	    }
	  }
	}
}

H5 - Canvas 技术详解_第16张图片

7.3 画布的缩放:scale()

scale(x, y):用来 增减 图形在 画布 中的 像素数目,对形状,位图进行 缩小,放大。

  • x 为水平缩放因子,y 为垂直缩放因子。
    • 默认值为1, 为实际大小。如果比1小,会比缩放图形, 如果比1大会放大图形。
    • 两个参数都是实数,可以为负数(相当于以x 或 y轴作为对称轴镜像反转)。

如果是图片的缩放,则 推荐使用 drawImage()缩放 方法。

示例:

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
  	  var ctx = canvas..getContext('2d');
  	  
	  // 画一个正方形,然后左右放大10倍,上下放大3倍
	  ctx.save();
	  ctx.scale(10, 3);
	  ctx.fillRect(1, 10, 10, 10);
	  ctx.restore();
	
	  // // x轴 -1,表示 "MDN" 沿 左侧边缘垂直方向 进行了 镜像反转
	  ctx.scale(-1, 1);
	  ctx.font = '48px serif';
	  ctx.fillText('MDN', -135, 120);
	}
}

H5 - Canvas 技术详解_第17张图片

7.4 画布的旋转:rotate()

rotate(angle):它用于以 原点 为中心旋转画布。

  • angle:表示旋转的角度。它是顺时针方向的,以弧度为单位的值。1PI 弧度 对应 180度

示例:

// 这里可以不保存状态,因为旋转的属性不会叠加,不受影响

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
	var ctx = canvas..getContext('2d');
	
    // 移动原点的位置
    ctx.translate(75, 75);
    // 6 环
    for (var i=1; i<6; i++) {
        // 改变每环的颜色,和旋转角度
        ctx.fillStyle = 'rgb(' + (51*i) + ',' + (255-51*i) + ',255)';
        
        // 每环的圆数量依次加倍
        for (var j=0; j<i*6; j++) {
            // 逆时针旋转
            ctx.rotate(Math.PI*2/(i*6));
            // 清空画布,防止重复绘制之前的路径
            ctx.beginPath();
            ctx.arc(0, i*12.5, 5, 0, Math.PI*2, true);
            ctx.fill();
        }
      }
	}
}

H5 - Canvas 技术详解_第18张图片

7.5 综合变形:transform()

transform():通过修改变形矩阵 参数,来同时实现 缩放旋转移动

  • 变形矩阵 原理图:

H5 - Canvas 技术详解_第19张图片

  • (x’, y’) 是变形后的坐标,(x, y) 是原始坐标。
  • 根据中间那个矩阵的不同,我们就可以得到不同的变换效果。
  • 矩阵对应的参数:
    m11 m21 dx
    m12 m22 dy
    0 0 1
  • 每个参数的意义:
    • m11:水平方向的缩放
    • m12:水平方向的 倾斜 偏移
    • m21:竖直方向的 倾斜 偏移
    • m22:竖直方向的缩放
    • dx:水平方向的移动
    • dy:竖直方向的移动
  • 使用方法:

    • transform(m11, m12, m21, m22, dx, dy):这 6 个 参数对应的 变形矩阵的 前 6 位。在之前的变换基础上接着进行变形。
    • setTransform(m11, m12, m21, m22, dx, dy):取消了当前变形,然后设置新的变形。这里是直接重置,然后变形。
    • resetTransform():重置所有变形。等效于 setTransform(1, 0, 0, 1, 0, 0)
  • 使用技巧:

    • 移动:a,b 为 x, y 轴的移动距离,则移动方法为 transform(1,0,0,1,a,b)
    • 缩放:a,b 为 x, y 轴的缩放因子,则缩放方法为 transform(a,0,0,b,0,0)
    • 旋转:angle 为旋转弧度,则旋转方法为 transform(Math.cos(angle),Math.sin(angle),-Math.sin(angle),Math.cos(angle),0 ,0)
      注意倾斜偏移 在实现 旋转 的过程中,会导致图形 “放大”,所以必须同时设置 缩放因子 进行缩小。
      关于 倾斜偏移 转化为 旋转 过程的计算方法,涉及数学知识,暂时不讨论,记住怎么使用就行了。
      包括后面案例中的 渲染星星 的方法,也是涉及数学知识,需要理解具体算法。

示例:

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
	  var ctx = canvas..getContext('2d');
	
	  var sin = Math.sin(Math.PI/6);
	  var cos = Math.cos(Math.PI/6);
	  
	  ctx.translate(100, 100);
	  
	  var c = 0;
	  for (var i=0; i <= 12; i++) {
	    c = Math.floor(255 / 12 * i);
	    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
	    ctx.fillRect(0, 0, 100, 10);
	    // 顺时针旋转 30 度
	    ctx.transform(cos, sin, -sin, cos, 0, 0);
	  }
	
	  // 重置变形(包括移动,缩放,旋转 等)
	  // 然后将后面 画的矩形,向 右侧 和 下方 都移动 100 个单位,然后沿 左侧垂直 镜像翻转
	  ctx.setTransform(-1, 0, 0, 1, 100, 100);
	  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
	  ctx.fillRect(0, 50, 100, 100);
	}
}

H5 - Canvas 技术详解_第20张图片

7.6 变形总结

  • 变形要点:在画布中,先确定坐标系,再画图。
  • 移动,缩放,旋转,都是针对 坐标系 的,而不是 图形 本身。

8. 合成与裁剪

8.1 合成属性:globalCompositeOperation

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 屏幕相乘,类似于叠加,但 上下图层 互换了。
soft-light 用顶层减去底层或者相反来得到一个正值。
difference 一个柔和版本的强光。纯黑或纯白不会导致纯黑或纯白。
exclusion 和 difference 相似,但对比度较低。
saturation 保留底层的 亮度 和 色调,同时采用顶层的 色度。
color 保留了底层的 亮度,同时采用了顶层的 色调 和 色度。
hue 保留了底层的 色度 和 亮度,同时采用了顶层的 色调。
luminosity 保持底层的 色度 和 色调,同时采用顶层的 亮度。

8.2 裁剪:clip()

  • 裁剪:是指将 路径所在的区域 从画布上 裁剪 出来,之后所有的 图形 都只会在 裁剪区域 出现,其他地方的 图形 会被隐藏
  • 和上面介绍的 globalCompositeOperation 属性作比较的话,它可以实现与 source-insource-atop 差不多的效果。
  • 最重要的区别是:裁切 不会在 画布 上绘制东西,而且它永远不受 新图形 的影响。

绘制形状 一章中,我只介绍了 stroke()fill() 方法,这里介绍第 3 个方法 clip(),即 裁剪

  • clip():将当前正在 构建的路径 转换为 当前的 裁剪路径
    默认情况下,画布 有一个与它 自身一样大 的裁切路径(也就是没有裁切效果)。

示例:用一个圆形的裁切路径来限制随机星星的绘制区域

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

	  ctx.fillRect(0,0,150,150);
	  ctx.translate(75,75);
	
	  // 创建裁剪路径
	  ctx.beginPath();
	  ctx.arc(0,0,60,0,Math.PI*2,true);
	  ctx.clip();
	
	  // 绘制蓝色渐变背景
	  var lingrad = ctx.createLinearGradient(0,-75,0,75);
	  lingrad.addColorStop(0, '#232256');
	  lingrad.addColorStop(1, '#143778');
	  ctx.fillStyle = lingrad;
	  ctx.fillRect(-75,-75,150,150);
	
	  // 随机添加 白色小星星
	  for (var j=1; j<50; j++){
	    ctx.save();
	    ctx.fillStyle = '#fff';
	    ctx.translate(75-Math.floor(Math.random()*150), 75-Math.floor(Math.random()*150));
	    drawStar(ctx, Math.floor(Math.random()*4) + 2);
	    ctx.restore();
	  }
	}
}


// 渲染星星方法
// r 表示星星外圆的半径
function drawStar(ctx, r){
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(r,0);
  for (var i=0; i<9; i++){
    ctx.rotate(Math.PI/5);
    
    if(i%2 == 0) {
      ctx.lineTo((r/0.525731)*0.200811, 0);
    } else {
      ctx.lineTo(r,0);
    }
  }
  ctx.fill();
  ctx.restore();
}

H5 - Canvas 技术详解_第21张图片

8.2.1 渲染星星的方法

上个示例中,使用了 渲染星星 的算法,可以根据下面的 参考图 进行理解。

星星渲染 算法 参考图:
H5 - Canvas 技术详解_第22张图片

9. 基本动画

由于我们是用 JavaScript 去操控 对象,这样要实现一些交互动画也是相当容易的。
可能最大的限制就是图像一旦绘制出来,它就是一直保持那样了。如果需要移动它,我们不得不对所有东西(包括之前的)进行重绘。重绘是相当费时的,而且性能很依赖于电脑的速度

9.1 动画的基本步骤:画出一帧

  • 1. 清空 canvasclearRect()
    除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有,被清空的 画布区域为 透明
  • 2. 保存 canvas 状态save()
    如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
  • 3. 绘制动画图形
    这一步才是 重绘 动画帧,比如 路径,样式,文本,图像,变形,裁剪 等等。
  • 4. 恢复 canvas 状态,继续画下一帧restore()
    如果已经保存了 canvas 的状态,可以先恢复它,然后 重绘 下一帧。

9.2 操控动画的每一帧

核心思想:在特定的 时间间隔 里,渲染一帧 图像,形成 连续的 动画。当 帧率 越高,动画 视觉效果 越好。

控制时间间隔,进行帧的渲染,有 2 种方法:

1 . 使用 定时器 进行控制:帧率可控

  • setInterval(function, delay):定期执行指定代码,循环执行。
  • setTimeout(function, delay):定期执行指定代码,执行 1 次。

2 . 使用 重绘方法 进行控制:帧率不可控

  • var raf = window.requestAnimationFrame(draw) :告诉浏览器你希望 重绘 一个动画。即在下次重绘 之前,会请求浏览器执>行一个特定的函数 draw() 来重绘 1 次 动画。
    所以,需要使用 嵌套循环,实现 不间断更新:通过在 特定函数 draw() 中 循环自调用 requestAnimationFrame(draw) 来实现 帧 的 连续渲染
    当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟 回调函数 执行 60 次,也有可能会被降低。
  • window.cancelAnimationFrame(raf)取消 重绘 函数,注意参数是 重绘函数 的返回值。下一节的 高级动画 示例中有用到。
9.2.1 requestAnimationFrame() 示例 1:太阳系模拟动画
// 创建图片源
// 太阳的图片,包括了太阳 和 星空背景
var sun = new Image();
var moon = new Image();
var earth = new Image();

// 初始化
function init() {
  // 给图片源添加地址
  sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png';
  moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png';
  earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png';
  
  // 启动动画第一帧
  window.requestAnimationFrame(draw);
}

// 每一帧的实现函数
function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
	  var ctx = canvas.getContext('2d');

	  // 设置新图形的合成策略,默认为 source-over ,在旧图形上方叠加。
	  // destination-over 表示在旧图形后面绘制。
	  // 因为这里先渲染的地球,月球,轨道,最后渲染的 太阳和星空背景,
	  // 所以采用 destination-over,当然也可以改变渲染顺序,使用默认值 source-over
	  ctx.globalCompositeOperation = 'destination-over';
		
	  // 1. 清空指定区域画布
	  ctx.clearRect(0,0,300,300);
	  
	  // 2. 保存 canvas 初始状态
	  ctx.save();
	  
	  // 3. 开始绘图
	  // 渲染地球
	  
	  // 设置地球旋转中心
	  ctx.translate(150,150);
	  // 设置渲染弧度
	  var time = new Date();
	  // 每帧旋转的弧度,按时间计算,设定 1分钟 转一圈
	  // 则 弧度 = 当前的秒数 * 每秒的弧度 + 当前的毫秒数  * 每毫秒的弧度
	  // 所以每帧的弧度都不一定,取决于每秒渲染的帧数
	  ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds());
	  // 设置月球旋转中心
	  ctx.translate(105,0);
	  // 放置地球图片
	  ctx.drawImage(earth,-12,-12);
	 
	  // 渲染月球
	  
	  // 设置渲染弧度
	  ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() );
	  // 设置月球中心
	  ctx.translate(0,28.5);
	  // 放置月球图片
	  ctx.drawImage(moon,-3.5,-3.5);
	  
	  // 先画轨道
	  // 回到初始状态
	  ctx.restore();
	  ctx.strokeStyle = 'rgba(0,153,255,0.4)';
	  ctx.beginPath();
	  ctx.arc(150,150,105,0,Math.PI*2);
	  ctx.stroke();
	 // 后放置太阳和星空背景
	  ctx.drawImage(sun,0,0,300,300);
		
	  // 开启帧的渲染,每秒 60 帧
	  window.requestAnimationFrame(draw);
	}
}

// 初始化,并启动动画
init();

H5 - Canvas 技术详解_第23张图片

9.2.2 requestAnimationFrame() 示例 2:动画时钟
function clock () {
  // 获取画布上下文
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
	  var ctx = canvas.getContext('2d');
  
	  // 1. 清空 canvas 指定区域
	  ctx.clearRect(0,0,150,150);
	  // 2. 保存画布初始状态
	  ctx.save();
	  
	  // 3. 开始绘图
	  // 设置默认参数
	  // 改变画布中心,右移 75像素,下移 75 像素
	  ctx.translate(75,75);
	  // 设置默认线条末端样式,半圆。默认是 butt,与边界平齐
	  ctx.lineCap = "round";
	  // 设置默认线条颜色,黑色
	  ctx.strokeStyle = "black";
	  // 设置缩放因子,用来整体控制时钟的大小
	  ctx.scale(0.4,0.4);
	  // 起点本来是在右侧开始的,这里需要还原到 0 点开始
	  ctx.rotate(-Math.PI/2);
	  // 保存基础配置状态
	  ctx.save();
	  
	  // 小时标记
	  for (var i = 0; i < 12; i++) {
	    ctx.beginPath();
	    //设置小时标记的线宽,8,默认为 1
	    ctx.lineWidth = 8;
	    // 设置小时标记的旋转弧度,顺时针旋转 30°。
	    // 记住旋转的只是画布。水平移动是相对于画布而言的。
	    ctx.rotate(Math.PI/6);
	    // 设置起点,在画布中心水平右移 100 像素
	    ctx.moveTo(100,0);
	    // 从起点,水平向右画一条 20 像素的直线
	    ctx.lineTo(120,0);
	    // 填充渲染
	    ctx.stroke();
	  }
	  // 恢复到基础配置状态
	  ctx.restore();
	
	  // 分钟标记
	  // 保存基础配置状态
	  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();
	    }
	    // 2PI / 60,为每分钟画布旋转的弧度
	    ctx.rotate(Math.PI/30);
	  }
	  // 恢复到基础配置状态
	  ctx.restore();
	 
	 // 获取本地时间
	  var now = new Date();
	  var sec = now.getSeconds();
	  var min = now.getMinutes();
	  var hr  = now.getHours();
	  // 格式化为 12 小时制
	  hr = hr>=12 ? hr-12 : hr;
	
	  // 小时指针
	  // 保存基础配置状态
	  ctx.save();
	  // 计算当前时间,小时指针的旋转弧度
	  ctx.rotate( hr * (Math.PI/6) + min * (Math.PI/360) + sec * (Math.PI/21600) );
	  // 设置小时指针线宽
	  ctx.lineWidth = 14;
	  // 开始绘制
	  ctx.beginPath();
	  ctx.moveTo(-20,0);
	  ctx.lineTo(80,0);
	  ctx.stroke();
	  // 恢复到基础配置状态
	  ctx.restore();
	
	  // 分钟指针
	  // 保存基础配置状态
	  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();
	 
	  // 秒指针
	  // 保存基础配置状态
	  ctx.save();
	  ctx.rotate(sec * Math.PI/30);
	 // 秒指针线条颜色,红色
	  ctx.strokeStyle = "#D40000";
	  // 秒指针线宽
	  ctx.lineWidth = 6;
	  ctx.beginPath();
	  ctx.moveTo(-30,0);
	  ctx.lineTo(83,0);
	  ctx.stroke();  
	  // 绘制秒指针末端
	  ctx.beginPath();
	  ctx.arc(95,0,10,0,Math.PI*2,true);
	  ctx.stroke();
	  // 恢复到基础配置状态
	  ctx.restore();
	
	  // 绘制时钟中心
	  // 设置中心填充颜色,红色
	  ctx.fillStyle = "#D40000";
	  ctx.beginPath();
	  ctx.arc(0,0,10,0,Math.PI*2,true);
	  ctx.fill();
	
	  // 绘制表盘圆环
	  ctx.beginPath();
	  ctx.lineWidth = 14;
	  // 设置线条颜色,蓝色
	  ctx.strokeStyle = '#325FA2';
	  ctx.arc(0,0,142,0,Math.PI*2,true);
	  ctx.stroke();
		
	  // 恢复到画布初始状态,避免影响下一帧的渲染
	  ctx.restore();
	  
	  // 开启帧的渲染,每秒 60 帧
	  window.requestAnimationFrame(clock);
	}
}

window.requestAnimationFrame(clock);

H5 - Canvas 技术详解_第24张图片

9.2.3 setInterval() 示例:循环全景照片

可以随意找一张任何尺寸大于canvas的图片。

// 自定义图像参数
var img = new Image();
img.src = 'https://mdn.mozillademos.org/files/4553/Capitan_Meadows,_Yosemite_National_Park.jpg';

// 画布大小需要和下面的参数一致
var canvasXSize;
var canvasYSize;
// 刷新率,多少毫秒刷新一次
// var speed = 5;
// 也可以设置帧数, 60帧 看起来更流畅
var frames = 24;
var speed = Math.floor(1000/frames);
// 每次的移动量
var dx = 0.75;
// 图像的 水平 和 垂直 偏移量
var x = 0;
var y = 0;
// 图像的宽高
var imgW;
var imgH;
// 图像超出画布的距离
var diff; 
// 画布对象
var ctx;

// 图片加载成功后,开始设置参数
img.onload = function() {
    // 获取画布上下文
    var canvas = document.getElementById('canvas');
	if (canvas.getContext) {
	 	ctx = canvas.getContext('2d');
	 	
	 	// 获取画布的宽高
	 	canvasXSize = canvas.width;
	 	canvasYSize = canvas.height;
	 	
	 	// 获取图片的宽高
	    imgW = img.width;
	    imgH = img.height;
	    // 计算图像超出画布的距离
	    diff = canvasXSize - imgW;
	    // 设置默认水平偏移量,这里采用图像从左往右循环
	    // 如果图像宽度大于画布的宽度,则图像左侧 的横坐标应该为负值
	    if (diff < 0) {
	    	x = canvasXSize - imgW;
	        // 设置刷新率
	    	setInterval(draw, speed);
	    } else {
	        // 图像宽度如果小于画布宽度,则不需要刷新,直接放置图画
	        ctx.drawImage(img,x,y,imgW,imgH);
	    }
	}
}

function draw() {
	// 当水平偏移量大于画布宽度时,表示图像已经移动完毕,要重置偏移量
  	if (x > canvasXSize) { x = canvasXSize - imgW;}

 	// 清空画布
    ctx.clearRect(0,0,canvasXSize,canvasYSize);
    // 绘制主图
    ctx.drawImage(img,x,y,imgW,imgH);
   	// 为了循环的连续性,当原图左侧出现空白时,需要补足缺口
   	if (x > 0) {
   		ctx.drawImage(img, x - imgW,y,imgW,imgH);
   	}
    // 增加水平偏移量,使得下次刷新时,图像向右移动
    x += dx;
}

效果应该是连续的,只是制作动态图时,没有截取完整,所以会有卡顿现象:

9.3 基础动画绘制 小结

  1. 每开始一阶段绘制前,要记得 saverestore 状态,不要影响到其他的绘制。
  2. 记住 变换的永远都是 画布,也就是 坐标系,和 图像 没有关系。特别的旋转的时候,不要对坐标系有 固化思维

10. 高级动画 案例

上一节的 基本动画 实现了 物件移动 的方法。
接下来的 高级动画 会添加一些符合物理的 运动,让我们的动画更加高级。

10.1 小球运动 案例

小球运动 案例中 需要实现的 功能

  • 绘制出一个小球
  • 添加长尾效果
  • 添加碰撞检测
  • 添加移动速度
  • 添加加速度
  • 添加鼠标控制:默认 鼠标拖动 确定起点,单击 控制开始和暂停,双击重新确定起点
var canvas = document.getElementById('canvas');
var ctx;
var running = false; // 小球移动标志
var isStart = true; // 小球起点控制标志,用于鼠标起点控制
var raf; // 动画帧的执行对象,用于鼠标暂停控制
var delay = null;   // 解决单双击事件冲突的 延时器变量

// 球对象
var ball = {
  x: 100,
  y: 100,
  radius: 25,
  color: 'blue',
  vx: 5, // 单位时间内移动距离
  vy: 2,
  draw: function() {
	if (canvas.getContext) {
	 	ctx = canvas.getContext('2d');
	 	
	    ctx.beginPath();
	    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
	    ctx.fillStyle = this.color;
	    ctx.fill();
	 }
  }
};

// 移动小球
function draw() {
	  // 直接清除旧的图像,这样不会有长尾效果
	  // ctx.clearRect(0, 0, canvas.width, canvas.height);
	  // 使用半透明填充旧的画布,这样就会产生长尾效果
	  ctx.fillStyle = 'rgba(255,255,255,0.3)';
	  ctx.fillRect(0,0,canvas.width,canvas.height);
	  
	  // 重新绘制小球
	  ball.draw();
	
	  // 碰撞检测
	  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
	    ball.vy = -ball.vy;
	  }
	  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
	    ball.vx = -ball.vx;
	  }
	  
	  // 改变速度,产生加速度,当移动的位移大于了画布时,碰撞检测将会失效
	  // ball.vx *= 1.01;
	  // ball.vy *= 1.01;
	  
	  // 移动
	  ball.x += ball.vx;
	  ball.y += ball.vy;
	  
	  raf = window.requestAnimationFrame(draw);
	}
	

	// 鼠标移动时,控制小球起点位置
	canvas.addEventListener('mousemove', function(e){
	  // 小球没有运动的时候,并且起点可以控制
	  if (!running && isStart) {
		  ctx.clearRect(0,0, canvas.width, canvas.height);
		  ball.x = e.clientX;
		  ball.y = e.clientY;
		  ball.draw();
	  }	
	});

	// 鼠标控制动画的启动
	// 鼠标双击时,让小球暂停,并且接受起点控制
	canvas.addEventListener('dblclick', function(){
	    // 双击事件 会 触发 2 次 单击事件,第一次由 单击事件内部取消
	    // 第二次在这里取消, 防止冲突
	    if(delay) {
	      clearTimeout(delay);
	    }
	    
	    if (running) {
			window.cancelAnimationFrame(raf);
	    }
	    running = false;
		isStart = true;
	});
	
	// 鼠标单击时,小球开始/暂停运动
	// js中 单击事件 和 双击事件 会有冲突,这里需要给单击事件设置 延迟执行定时器 delay 
	canvas.addEventListener('click', function(){
	  // 清除上一次的单击事件, 防止冲突
	  if(delay) {
	    clearTimeout(delay);
	  }
	  
	  delay = setTimeout(function(){
		  isStart = false;
		  if (!running) {
		    window.requestAnimationFrame(draw);
		    running = true;
		  } else {
			window.cancelAnimationFrame(raf);
			running = false;
		  }
	  }, 400); // 大于 300 毫秒即可
});

// 绘制小球
ball.draw();

H5 - Canvas 技术详解_第25张图片

10.2 打砖块游戏(待完成)

有时间再补充。

11. 像素操作:

  • 像素的读取和写入:可以直接通过 ImageData 对象 操纵 画布 像素 数据。
  • 反锯齿:控制图像,使其平滑。
  • 保存图像:如何从Canvas画布中保存图像。

11.1 像素的读取和写入

11.1.1 ImageDate 对象的 创建,读取,写入

ImageDate 对象的 创建,读取,写入的3个方法:createImageData()getImageData()putImageData()

  • 创建 ImageData 对象:2 种方法
  • 创建空白 ImageData 对象:像素被预设为透明黑
    var myImageData = ctx.createImageData(width, height);
  • 借用 其他 ImageData 对象的像素,创建 ImageData 对象:
    var myImageData = ctx.createImageData(anotherImageData);
  • 读取 ImageData 对象 所属区域 的 像素数据
  • var myImageData = ctx.getImageData(x, y, width, height);
    • x,y:表示区域左上角的坐标
    • width, height:表示区域的宽和高
  • 在画布种 写入 像素数据:
  • ctx.putImageData(myImageData, dx, dy);:dx,dy 表示需要 写入像素数据 的画布区域的 左上角的坐标点。
11.1.2 ImageDate 对象的属性

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

  • width:图片宽度,单位是像素。
  • height:图片高度,单位是像素。
  • data:Uint8ClampedArray 类型的 一维数组,里面是初始像素数据。
    • 每个像素用 4 个1 bytes 值( 按照 绿透明值 的顺序,即 “RGBA” 格式 ) 来代表,范围在0至255之间(包括255)。
    • 左上角像素的红色部份在数组的索引 0 位置。像素从左到右被处理,然后往下,遍历整个数组。
    • Uint8ClampedArray 包含 高度 × 宽度 × 4 bytes数据
    • 索引值从 0(高度 × 宽度 × 4) - 1
  • 属性使用方法:
  1. 根据 行、列 读取 某像素点的 R/G/B/A 值的公式:

    imageData.data[((行数-1)*imageData.width + (列数-1))*4 - 1 + 1/2/3/4];
    
  2. 读取 像素数组的 大小

    // 图像的 像素数组 的长度,即是 图像的大小,单位是 byte
    var numBytes = imageData.data.length;
    
11.1.3 像素读取 案例:颜色读取器,getImageData()

在使用 getImageData() 读取 像素数组 时,会受到 跨域数据 的影响,所以需要设置 图像 为 允许跨域,如:img.crossOrigin ='anonymous';。同时,源图像也应将 access-control-allow-origin 设置为 * 或 选定的域,保证 浏览器 能够获取到。

示例中的 图片 来源于百度,如果跨域了,可以自行替换。


<html>
    <head>
        <title>代码测试title>
        <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    head>
    <body>
		<canvas id="canvas" width="400" height="300"  >canvas>
		<div id="color" style="width: 300px; height: 300px;">div>

        <script type="application/javascript">
			var canvas = document.getElementById("canvas");
			if (canvas.getContext) {
				var ctx = canvas.getContext("2d");
				var color = document.getElementById("color");
				var img = new Image();
				img.crossOrigin ='anonymous';
				img.src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1585163882240&di=b70e0a21d006f5c2c0fe54d138cb1fb8&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D1484500186%2C1503043093%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853";
	
				img.onload = function() {
					ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
					img.style.display = "none";
				};
	
				canvas.addEventListener("mousemove", pick);
			}

			function pick(e) {
					var x = e.clientX;
					var y = e.clientX;
					// 拿到鼠标点处 1 像素的数据
					var pixel = ctx.getImageData(x, y, 1, 1);
					var data = pixel.data;
					// 取该像素点的 RGBA 值
					var rgba =
					"rgba(" +
					data[0] +
					", " +
					data[1] +
					", " +
					data[2] +
					", " +
					(data[3] / 255).toFixed(1) +
					")";
					
					color.style.background = rgba;
					color.innerText = 'x: ' + x + '\ny: ' + y + '\n' + rgba;
				}
        script>
    body>
html>

H5 - Canvas 技术详解_第26张图片

11.1.3.1 获取 图像 的 像素数据时,受跨域影响,解决方案
  1. img.src 前,必须先设置 img.crossOrigin ='anonymous' 以 获取到图像的数据。
  2. 源图像的服务器 应将 access-control-allow-origin 设置为 *选定的域
11.1.4 像素写入案例:颜色反相 和 图片灰度,putImageData()

思路:遍历所有像素 并 修改,然后我们将被修改的 像素数组 通过 putImageData() 放回到画布中去。

示例中的 图片 来源于百度,如果跨域了,可以自行替换。


<html>
    <head>
        <title>代码测试title>
        <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    head>
    <body>
		<canvas id="canvas" width="400" height="300"  >canvas><br />
		<input type="button" id="invertbtn" value="颜色反相" />
		<input type="button" id="grayscalebtn" value="图片灰度" />
		<input type="button" id="backbtn" value="还原" />

        <script type="application/javascript">
			var canvas = document.getElementById("canvas");

			if (canvas.getContext) {
				var ctx = canvas.getContext("2d");
				var invertbtn = document.getElementById("invertbtn");
				var grayscalebtn = document.getElementById("grayscalebtn");
				var backbtn = document.getElementById("backbtn");

				var canvasW = canvas.width;
				var canvasH = canvas.height;
				var imageData;
				// imageData.data 是只读属性,所以创建一个 data 变量来存储
				var data;
				var img = new Image();
				img.crossOrigin ='anonymous';
				img.src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1585163882240&di=b70e0a21d006f5c2c0fe54d138cb1fb8&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D1484500186%2C1503043093%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853";
	
				img.onload = function() {
					ctx.drawImage(img, 0, 0, canvasW, canvasH);

					invertbtn.addEventListener("click", invert);
					grayscalebtn.addEventListener("click", grayscale);
					backbtn.addEventListener("click", back);
				};

				// 反转颜色
				var invert = function() {
					back();

					for (var i = 0; i < data.length; i += 4) {
						// red
						data[i] = 255 - data[i];
						// green
						data[i + 1] = 255 - data[i + 1];
						// blue
						data[i + 2] = 255 - data[i + 2];
						data[i + 3] = data[i + 3];
					}

					ctx.putImageData(imageData, 0, 0);
				};
		
				// 图片灰度
				var grayscale = function() {
					back();

					for (var i = 0; i < data.length; i += 4) {
						var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
						// red
						data[i] = avg;
						// green
						data[i + 1] = avg;
						// blue
						data[i + 2] = avg;
						data[i + 3] = data[i + 3];
					}

					ctx.putImageData(imageData, 0, 0);
				};

				// 还原,重置画布
				var back = function() {
					// 单独获取原始画布像素集,而不是其他动作变化后的画布,所以要重新绘制画布
					ctx.drawImage(img, 0, 0, canvasW, canvasH);
					imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
					// 传递引用,‘修改’ 只读属性
					data = imageData.data;
				};
			}

        script>
    body>
html>

H5 - Canvas 技术详解_第27张图片

11.2 缩放 和 反锯齿:imageSmoothingEnabled 属性

  • 缩放:通过 drawImage()缩放 方法可以实现 图片缩放的效果,并且可以指定 缩放 位置大小
    scale() 函数是 用来缩放画布,并且只是指定缩放的 倍数,不适合 图片的缩放。
  • 反锯齿:设置 imageSmoothingEnabled = false 属性来关闭它,以看到 清楚的像素
    • 反锯齿 默认是 启用 的。
11.2.1 缩放 和 反锯齿 综合示例

示例中的 图片 来源于百度,如果跨域了,可以自行替换。


<html>
    <head>
        <title>代码测试title>
        <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    head>
    <body>
		<canvas id="canvas" width="400" height="300"  >canvas>
		<canvas id="zoom" width="200" height="200">canvas><br />
		<input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn">
		<label for="smoothbtn">Enable image smoothinglabel>

        <script type="application/javascript">
			var canvas = document.getElementById("canvas");

			if (canvas.getContext) {
				var ctx = canvas.getContext("2d");
				var zoomctx = document.getElementById("zoom").getContext("2d");
				var smoothbtn = document.getElementById("smoothbtn");

				var canvasW = canvas.width;
				var canvasH = canvas.height;
				var img = new Image();
				img.crossOrigin ='anonymous';
				img.src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1585163882240&di=b70e0a21d006f5c2c0fe54d138cb1fb8&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D1484500186%2C1503043093%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853";
	
				img.onload = function() {
					ctx.drawImage(img, 0, 0, canvasW, canvasH);

					smoothbtn.addEventListener("change", toggleSmoothing);
					canvas.addEventListener("mousemove", zoom);
				};

				// 反锯齿设置事件
				var toggleSmoothing = function() {
					zoomctx.imageSmoothingEnabled = this.checked;
					ctx.imageSmoothingEnabled = this.checked;
					ctx.drawImage(img, 0, 0, canvasW, canvasH);

					// 不同浏览器需要不同前缀
					zoomctx.mozImageSmoothingEnabled = this.checked;
					zoomctx.webkitImageSmoothingEnabled = this.checked;
					zoomctx.msImageSmoothingEnabled = this.checked;
					ctx.mozImageSmoothingEnabled = this.checked;
					ctx.webkitImageSmoothingEnabled = this.checked;
					ctx.msImageSmoothingEnabled = this.checked;
				};

				// 鼠标控制缩放,切片放大后的图像显示到另一个画布
				var zoom = function(e) {
					var x = e.clientX;
					var y = e.clientY;
					zoomctx.drawImage(canvas, Math.abs(x - 10), Math.abs(y - 10), 20, 20, 0, 0, 200, 200);
				};
			}
        script>
    body>
html>

11.3 保存图片

  • HTMLCanvasElement 提供了 2 种,生成 图像链接 URL 的方法:toDataURL()toBlob()
  • 生成的 URL 可以 放在一个有 download 属性的 超链接里,或 用于 元素。
11.3.1 直接生成 图片URL:toDataURL()
  • 使用方法:

    • canvas.toDataURL('image/png'):默认设定。创建一个 PNG 图片。
    • canvas.toDataURL('image/jpeg', quality):创建一个 JPG 图片。
      • quality:品质量,0 基本不被辨析但有比较小的文件大小,1 表示最好品质。
  • 返回值:包含被 类型参数 规定的 图像表现格式 的 数据链接。返回的图片分辨率是 96dpi

代码示例:

<a id="a" download>下载a>

<script>
	var canvas = document.getElementById("canvas");
	document.getElementById('a').href = canvas.toDataURL('image/jpeg', 1);
script>
11.3.1 通过图片对象 Blob 生成 图片URL
  • 使用方法:
    • canvas.toBlob(callback, type, encoderOptions):
      • callback:回调函数,参数是该画布的 Blob 图片对象
      • type:图片格式,默认格式为 image/png
      • encoderOptions:Number 类型,值在0与1之间。当请求图片格式为 image/jpegimage/webp 时用来指定图片展示质量。

代码示例:

var canvas = document.getElementById("canvas");

canvas.toBlob(function(blob) {
  // 新建载体
  var newImg = document.createElement("img");
  // 获取原画布的图像链接
  var url = URL.createObjectURL(blob);
  // 设置给载体
  newImg.src = url;

  newImg.onload = function() {
    // 已经不需要 blob 了,现在撤销掉
    URL.revokeObjectURL(url);
    
    document.body.appendChild(newImg);
  };
}, "image/jpeg", 0.95);

12. canvas 的优化

元素是众多广泛使用的 网络2D图像 渲染标准之一。它被广泛用于 游戏 及 复杂的 图像可视化 中。然而,随着网站和应用将 canvas 画布推至极限,性能 开始成为问题。

12.1 优化建议

  • 在 离屏canvas上 预渲染 相似的图形 或 重复的对象:
    如果你发现你的在每一帧里有好多 复杂 的画图运算,请考虑创建一个离屏canvas,将图像在这个画布上画一次,然后在渲染给 页面里的画布。

    myEntity.offscreenCanvas = document.createElement("canvas");
    myEntity.offscreenCanvas.width = myEntity.width;
    myEntity.offscreenCanvas.height = myEntity.height;
    myEntity.offscreenContext = myEntity.offscreenCanvas.getContext("2d");
    
    myEntity.render(myEntity.offscreenContext);
    
  • 避免浮点数 的坐标点,用整数取而代之:
    当你画一个没有整数坐标点的对象时会发生 子像素渲染,如:
    ctx.drawImage(myImage, 0.3, 0.5);
    浏览器为了达到抗锯齿的效果会做额外的运算。为了避免这种情况,请保证在你调用 drawImage() 函数时,用 Math.floor() 函数对所有的 坐标点 取整。

  • 使用 多层画布 去画一个复杂的场景:
    你可能会发现,你有些元素不断地改变或者移动,而其它的元素,例如 外观,永远不变。这种情况的一种优化是去 用多个画布元素 去创建 不同层次
    例如,你可以在最顶层创建一个外观层,而且仅仅在用户输入的时候被画出。你可以创建一个游戏层,在上面会有不断更新的元素和一个背景层,给那些较少更新的元素。

    <div id="stage">
      <canvas id="ui-layer" width="480" height="320">canvas>
      <canvas id="game-layer" width="480" height="320">canvas>
      <canvas id="background-layer" width="480" height="320">canvas>
    div>
     
    <style>
      #stage {
        width: 480px;
        height: 320px;
        position: relative;
        border: 2px solid black
      }
      canvas { position: absolute; }
      #ui-layer { z-index: 3 }
      #game-layer { z-index: 2 }
      #background-layer { z-index: 1 }
    style>
    
  • 用 CSS 设置大的 静态背景图
    比如像大多数游戏那样,你有一张静态的背景图,用一个静态的

    元素,结合background 特性。将它置于画布元素后面。画布 默认是透明的。
    这么做可以避免在 每一帧 在画布上绘制 背景图。

  • 不要在用 drawImage缩放 图像:
    在 离屏canvas 中缓存图片不同尺寸,而不要用 drawImage()缩放它们。

  • CSStransforms 特性缩放画布:
    CSS transforms 特性由于调用GPU,因此更快捷。
    最好的情况是:不要将小画布放大,而是去将 大画布缩小
    例如:Firefox系统,目标分辨率 480 x 320 px

    var scaleX = canvas.width / window.innerWidth;
    var scaleY = canvas.height / window.innerHeight;
    
    // 适应模式
    var scaleToFit = Math.min(scaleX, scaleY);
    // 覆盖模式
    var scaleToCover = Math.max(scaleX, scaleY);
    
    stage.style.transformOrigin = '0 0'; // 从左上角开始
    stage.style.transform = 'scale(' + scaleToFit + ')';
    
  • 关闭 上下文的 透明度
    如果你的游戏 使用画布 而且 不需要透明,当使用 HTMLCanvasElement.getContext() 创建一个绘图上下文时,把 alpha 选项设置为 false 。这个选项可以帮助浏览器进行内部优化。

    var ctx = canvas.getContext('2d', { alpha: false });
    
  • 将画布的函数 调用集合 到一起(例如,画一条折线,而不要画多条分开的直线)。

  • 避免不必要的 画布状态改变

  • 渲染 画布中的 不同点,而非整个新状态。

  • 尽可能避免 shadowBlur 特性,即 第 4.6 节讲的 阴影属性 里的 模糊效果程度的。

  • 尽可能避免 text rendering,即第 5 节讲的 绘制文本。

  • 使用不同的办法去清除画布:clearRect() vs fillRect() vs 调整canvas大小

  • 有动画,请使用 window.requestAnimationFrame() 而非 定时器 window.setInterval()。

  • 请谨慎使用大型物理库。

13 其他

13.1 位图(Canvas) 和 矢量图(SVG) 的区别

SVG 技术详解:《SVG 技术详解》 博文。

图类 属性 特点 绘图工具
位图 像素(图片元素)的单个点组成的,这些点可以进行不同的排列和染色以构成图样,当放大位图时,可以看见赖以构成整个图像的无数 单个方块 可以表现 色彩的变化 和 颜色的细微过渡 ,产生逼真的效果,缺点是图片 放大会失真 使用 Photoshop、Painter 和 Windows系统自带的画图工具 制图
矢量图 线 连接的点,矢量文件中的图形元素称为 对象。每个对象都是一个自成一体的 实体,它具有颜色、形状、轮廓、大小和屏幕位置等属性 放大后图像 不会失真,和分辨率无关。适用于 图形设计文字设计 和 一些 标志设计版式设计等。 使用 Adobe Illustrator、CorelDRAW、FlashMX 制图

13.2 其他图形相关的 API

  • SVG:可缩放矢量图形(简称SVG)允许你使用矢量线,矢量图形,确保无论图片大小都可以比例平滑地显示。
  • WebGL:用于渲染交互式 3D 图形的API。
  • Web Audio:提供了 Web 上的音频控制——个强大和灵活的系统,允许开发人员选择的音频源,添加效果的音频,创建音频可视化,应用空间效应(如平移)等等。

13.3 相关应用教程

  • 游戏开发

你可能感兴趣的:(前端技术栈)