Canvas 基础

一 认识 canvas

canvas 标签只有两个属性**——** width 和 height

简单一个简单的矩形

<!-- id: 唯一标识  width: 画布宽度  height: 画布高度 -->
<canvas id="c1" width="600" height="600"></canvas>
// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
// 绘制矩形(位置x, 位置y, 宽度, 高度)
ctx.fillRect(100, 200, 300, 300)

Canvas

二 判断兼容性

如果浏览器不支持 canvas,那就是个普通标签,可以通过 canvas 标签内部来包裹内容,在不支持 canvas 的时候显示

<!-- id: 唯一标识  width: 画布宽度  height: 画布高度 -->
<canvas id="c1" width="600" height="400">
  当前浏览器不支持canvas, 请下载最新的浏览器
  <a href="https://www.google.com/chrome/">立即下载</a>
</canvas>

可以通过检查是否有 getContext() 方法的存在,来判断是否支持 canvas

// 1 找到画布
const c1 = document.getElementById('c1')

// 判断是否有getContext
if (!c1.getContext) {
  console.log('当前浏览器不支持canvas, 请下载最新的浏览器')
}
// TODO 支持canvas...

三 绘制基础图像

canvas 的坐标轴体系: 以左上角为起点(0, 0),从上到下,从左往右

Canvas

1 绘制矩形-三种

<!-- id: 唯一标识  width: 画布宽度  height: 画布高度 -->
<canvas id="c1" width="600" height="400"></canvas>

1 绘制一个填充的矩形 fillRect(x, y, width, height)

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
// 绘制矩形(位置x, 位置y, 宽度, 高度)
ctx.fillRect(100, 200, 300, 300)

2 绘制一个矩形的边框 strokeRect(x, y, width, height)

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
// 路径绘制矩形
ctx.strokeRect(100, 100, 200, 100)

3 清除指定矩形区域,让清除部分完全透明 clearRect(x, y, width, height)

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
// 绘制矩形(位置x, 位置y, 宽度, 高度)

// 绘制最外层矩形
ctx.beginPath()
ctx.rect(100, 100, 200, 100)
ctx.stroke()
ctx.closePath()

// 绘制最内层矩形
ctx.beginPath()
ctx.rect(150, 125, 100, 50)
ctx.fill()
ctx.closePath()

// 清除一块
ctx.clearRect(175, 145, 20, 20)

Canvas

2 绘制路径

beginPath() 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径

closePath() 闭合路径之后图形绘制命令又重新指向到上下文中

stroke() 通过线条来绘制图形轮廓

fill() 通过填充路径的内容区域生成实心的图形

3 绘制圆弧

arc(x, y, radius, startAngle, endAngle, anticlockwise)

arc(圆心 x, 圆心 y, 圆半径 radius, 开始的角度, 结束的角度, 逆时针还是顺时针 - 默认是顺时针 false, 可以设置逆时针为 true)

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.arc(300, 200, 50, 0, Math.PI / 2)
ctx.stroke()

Canvas

arcTo(x1, y1, x2, y2, radius)

arcTo(点 1x, 点 1y, 点 2x, 点 2y, 圆半径 radius)

PS: arcTo()方法将利用当前端点、端点 1(x1,y1)和端点 2(x2,y2)这三个点所形成的夹角,然后绘制一段与夹角的两边相切并且半径为 radius 的圆上的弧线。弧线的起点就是当前端点所在边与圆的切点,弧线的终点就是端点 2(x2,y2)所在边与圆的切点,并且绘制的弧线是两个切点之间长度最短的那个圆弧。此外,如果当前端点不是弧线起点,arcTo()方法还将添加一条当前端点到弧线起点的直线线段。

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()

// (1) 指定绘制路径的起始点
ctx.moveTo(50, 50)
// (2) 绘制一条到坐标(150,50)的水平直线
// 不画会自动补充
// ctx.lineTo(150, 50)
// (3) 绘制与当前端点、端点1、端点2三个点所形成的夹角的两边相切并且半径为50px的圆的一段弧线
ctx.arcTo(200, 50, 200, 100, 50)
// (4) 按照上述绘制路径绘制弧线
ctx.stroke()

ctx.closePath()

Canvas

4 绘制笑脸

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
// 画一个脸
ctx.beginPath()
ctx.arc(75, 75, 50, 0, Math.PI * 2)
ctx.stroke()
ctx.closePath()

// 画嘴巴
ctx.beginPath()
ctx.arc(75, 75, 35, 0, Math.PI)
ctx.stroke()
ctx.closePath()

// 画左眼
ctx.beginPath()
ctx.arc(60, 65, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.closePath()

// 画右眼
ctx.beginPath()
ctx.arc(90, 65, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.closePath()

Canvas

5 移动笔触

moveTo(x, y) 将笔触移动到指定的坐标 x 以及 y 上

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()
// 画一个脸
ctx.arc(75, 75, 50, 0, Math.PI * 2)
ctx.moveTo(110, 75)
// 画嘴巴
ctx.arc(75, 75, 35, 0, Math.PI)
ctx.moveTo(65, 65)
// 画左眼
ctx.arc(60, 65, 5, 0, Math.PI * 2)
ctx.moveTo(95, 65)
// 画右眼
ctx.arc(90, 65, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.closePath()

Canvas

6 线段

lineTo(x, y) 绘制一条从当前位置到指定 x 以及 y 位置的直线

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()
ctx.moveTo(125, 125)
ctx.lineTo(125, 45)
ctx.stroke()
ctx.closePath()

7 三角形

lineTo(x, y) 绘制一条从当前位置到指定 x 以及 y 位置的直线

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
// 填充三角形
ctx.beginPath()
ctx.moveTo(25, 25)
ctx.lineTo(105, 25)
ctx.lineTo(25, 105)
ctx.fill()

// 描边三角形
ctx.beginPath()
ctx.moveTo(125, 125)
ctx.lineTo(125, 45)
ctx.lineTo(45, 125)
ctx.closePath()
ctx.stroke()

Canvas

8 二次贝塞尔曲线

三点确定弧线,原理如下:

Canvas

quadraticCurveTo(cp1x, cp1y, x, y)

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

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()

// (1) 指定绘制路径的起始点
ctx.moveTo(20, 20)
// (2) 指定控制点和结束点
ctx.quadraticCurveTo(20, 100, 200, 20)
// (3) 绘制
ctx.stroke()

ctx.closePath()

Canvas

案例-对话气泡

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()

// (1) 指定绘制路径的起始点
ctx.moveTo(200, 300)
// (2) 指定控制点和结束点
ctx.quadraticCurveTo(150, 300, 150, 200)
ctx.quadraticCurveTo(150, 100, 300, 100)
ctx.quadraticCurveTo(450, 100, 450, 200)
ctx.quadraticCurveTo(450, 300, 250, 300)
ctx.quadraticCurveTo(250, 350, 150, 350)
ctx.quadraticCurveTo(200, 350, 200, 300)
// (3) 绘制
ctx.stroke()

ctx.closePath()

Canvas

9 三次贝塞尔曲线

四点确定弧线,原理如下:

Canvas

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

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

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()

// (1) 指定绘制路径的起始点
ctx.moveTo(20, 20)
// (2) 指定控制点和结束点
ctx.bezierCurveTo(20, 100, 200, 100, 200, 20)
// (3) 绘制
ctx.stroke()

ctx.closePath()

Canvas

案例-心形

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
ctx.beginPath()

// (1) 指定绘制路径的起始点
ctx.moveTo(75, 40)
// (2) 指定控制点和结束点
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)
// (3) 绘制
ctx.stroke()
ctx.fill()

ctx.closePath()

Canvas

10 Path2D

  • Path2()可使用最新版本的浏览器中的对象来缓存或记录这些绘图命令。您可以快速播放路径
  • Path2D()构建函数返回一个新实例化的 Path2D 对象任意的与另一条路径作为参数(创建一个副本)或任选的用有一串 svg 路径数据
new Path2D() //空路径对象
new Path2D(path) // 从另一个Path2D对象复制
new Path2D(d) // SVG路径数据中的路径
// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像
const rectangle = new Path2D()
rectangle.rect(10, 10, 50, 50)

const circle = new Path2D()
circle.moveTo(125, 35)
circle.arc(100, 35, 25, 0, 2 * Math.PI)

ctx.stroke(rectangle)
ctx.fill(circle)

Canvas

使用 SVG 作为路径

新的 Path2D API 有另一个强大的特点,就是使用 SVG path data 来初始化 canvas 上的路径

// 1 找到画布
const c1 = document.getElementById('c1')

// 2 获取画笔,-上下文对象
const ctx = c1.getContext('2d')

// 3 绘制图像-正方形
// 先移动到点 (M10 10)
// 然后再水平移动 80 个单位(h 80)
// 然后下移 80 个单位 (v 80)
// 接着左移 80 个单位 (h -80)
// 再回到起点处 (z)
const p = new Path2D('M10 10 h 80 v 80 h -80 Z')
ctx.stroke(p)

Canvas

四 颜色和样式

1 设置颜色

fillStyle = color 设置图形的填充颜色

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

strokeStyle = color 设置图形轮廓的颜色

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

2 设置透明度

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

3 线性渐变

createLinearGradient(x1, y1, x2, y2) 表示渐变的起点 (x1,y1) 与终点 (x2,y2)

gradient.addColorStop(position, color)

  • position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间
  • color 参数必须是一个有效的 CSS 颜色值(如 #FFF,rgba(0,0,0,1),等等
const ctx = c1.getContext('2d')
// 1 创建线性渐变
const linearGradient = ctx.createLinearGradient(100, 200, 400, 500)
// 2 上色
linearGradient.addColorStop(0, 'red')
linearGradient.addColorStop(0.5, '#ffcccc')
linearGradient.addColorStop(1, 'blue')
// 3 作画
ctx.fillStyle = linearGradient
ctx.fillRect(100, 200, 300, 300)

Canvas

4 径向渐变

createRadialGradient(x1, y1, r1, x2, y2, r2) 表示以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆

const ctx = c1.getContext('2d')
// 1 创建线性渐变
const radialGradient = ctx.createRadialGradient(200, 150, 0, 200, 150, 100)
// 2 上色
radialGradient.addColorStop(0, 'red')
radialGradient.addColorStop(0.5, '#ffcccc')
radialGradient.addColorStop(1, 'blue')
// 3 作画
ctx.fillStyle = radialGradient
ctx.fillRect(0, 0, 400, 300)

Canvas

4 圆锥渐变

createConicGradient(角度, x, y) 角度可以使用 Math.PI 来计算,注意,角度是逆时针旋转的

const ctx = c1.getContext('2d')
// 1 创建线性渐变
const conicGradient = ctx.createConicGradient(0, 200, 150)
// 2 上色
conicGradient.addColorStop(0, 'red')
conicGradient.addColorStop(0.5, 'yellow')
conicGradient.addColorStop(1, 'blue')
// 3 作画
ctx.fillStyle = conicGradient
ctx.fillRect(0, 0, 400, 300)

Canvas

5 图案样式 Patterns

createPattern(image, type) image 图片地址 type 重复方式:repeat | repeat-x | repeat-y | no-repeat

const ctx = c1.getContext('2d')
// 图案样式 Patterns
const img = new Image()
img.src = '../image/money.png'
img.onload = function () {
  const pattern = ctx.createPattern(img, 'repeat')
  ctx.fillStyle = pattern
  ctx.fillRect(0, 0, 600, 380)
}

Canvas

6 线条颜色

每个属性说明见地址 MDN 线条open in new window

(1)lineWidth = value 设置线条宽度,属性值必须为正数。默认值是 1

(2)lineCap = type 设置线条末端样式,butt 平齐,round 半圆 和 square 正方形。默认是 butt

(3)lineJoin = type 设定线条与线条间接合处的样式,round 圆角, bevel 斜角 和 miter 尖角。默认是 miter

(4) miterLimit = value 对斜接面进行限制,值是数字,0、负数、Infinity 和 NaN 都会被忽略,默认是 10

const ctx = c1.getContext('2d')

// 线条
ctx.moveTo(150, 150)
ctx.lineTo(300, 200)
ctx.lineTo(450, 150)

// 设置线条宽度
ctx.lineWidth = 40
// 设置线条端点样式
ctx.lineCap = 'round'
// 设置线条链接处样式
ctx.lineJoin = 'mitter'
// 设置斜面限制
ctx.miterLimit = 10

ctx.stroke()

Canvas

7 虚线

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

(2)setLineDash(segments) 设置当前虚线样式

(3)lineDashOffset = value 设置虚线样式的起始偏移量

const ctx = c1.getContext('2d')

ctx.setLineDash([4, 2])
ctx.lineDashOffset = 10
ctx.strokeRect(10, 10, 100, 100)

Canvas

8 阴影

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

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

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

(4)shadowColor = color 必须是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色,

const ctx = c1.getContext('2d')

ctx.shadowOffsetX = 10
ctx.shadowOffsetY = 10
ctx.shadowBlur = 5
ctx.shadowColor = 'rgba(255, 100, 100, 1)'

ctx.strokeRect(10, 10, 100, 100)

Canvas

五 文本相关

1 字体简介

(1) 衬线字体和无衬线字体

Canvas 中字体分为衬线字体和无衬线字体

  • 衬线字体每个字的笔划有粗有细,衬线体棱角分明,长文阅读比较舒服
  • 而无衬线字体笔划粗细均匀,比较简洁美观,适用于单词短句,例如标题

Canvas

(2) 中文区分

  • 中文界的衬线体与无衬线体,两个有代表性的分类——宋体和黑体,分别对应着衬线字体和无衬线字体

Canvas

(3) 各种字体清晰度

Canvas

目前主流使用非衬线的,兼顾了 windows / mac 电脑/手机

'Hiragino Sans GB', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans - serif

2 font 属性

ctx.font = value; 符合 CSS font 语法的字符串。默认字体是 10px sans-serif

// 例如要设置字体为 48px 大小的微软雅黑,可以使用下面的方法
ctx.font = '48px Microsoft YaHei'

3 fillText 绘制文本

fillText(text, x, y [, maxWidth])

参数说明
text要绘制的文本
x文本起始点的 x 轴坐标
y文本起始点的 y 轴坐标
maxWidth可选,需要绘制的最大宽度。如果指定了值,并且经过计算字符串的宽度比最大宽度还要宽,字体为了适应会使用一个水平缩小的字体或者小号的字体
// 例如要在 (10,10) 位置绘制文本 简单教程,简单编程,可以如下使用
 ctx.fillText('hello world', 100, 100)

Canvas

4 strokeText 绘制文本边框

strokeText(text, x, y [, maxWidth])

参数说明
text要绘制的文本
x文本起始点的 x 轴坐标
y文本起始点的 y 轴坐标
maxWidth可选,需要绘制的最大宽度。如果指定了值,并且经过计算字符串的宽度比最大宽度还要宽,字体为了适应会使用一个水平缩小的字体或者小号的字体
// 例如要在 (10,10) 位置绘制文本 简单教程,简单编程,可以如下使用
 ctx.strokeText('hello world', 100, 100)

Canvas

5 textBaseline 文本基线

textBaseline = value

参数说明
top文本基线在文本块的顶部
hanging文本基线是悬挂基线
middle文本基线在文本块的中间
alphabetic文本基线是标准的字母基线,默认就是alphabetic
ideographic文字基线是表意字基线。如果字符本身超出了 alphabetic 基线,那么ideograhpic 基线位置在字符本身的底部
bottom文本基线在文本块的底部。与 ideographic 基线的区别在于 ideographic 基线不需要考虑下行字母
// 画基线
ctx.strokeStyle = "red"
ctx.moveTo(0,80)
ctx.lineTo(500,80)
ctx.stroke();

ctx.font = '48px Microsoft YaHei'

const textbase = ['top','hanging','middle','alphabetic','ideographic','bottom']
for ( var i = 0; i < textbase.length; i++){
    ctx.textBaseline = textbase[i];
    ctx.fillText("美", 10 + 60 * i, 80);
}

Canvas

补充知识:六线五格图

  • 如果不看 f ( 因为 f 比较特殊 ) 所有的文字都被限制在 六线五格 里面

Canvas

我们把这六条线从上到下命名为

top

hanging

middle

alphabetic

ideographic

bottom

6 direction 绘制方向

direction = value

参数说明
ltr文本方向从左向右
rtl文本方向从右向左
inherit默认值,根据情况继承 canvas> 元素或者 Document
因为这两个元素默认的方向是 ltr,所以默认的设置一般就是 ltr
ctx.direction = "ltr";
ctx.fillText("hello world", 80, 100);

Canvas

6 textAlign 水平对齐

textAlign = value

参数说明
left文本左对齐
right文本右对齐
center文本居中对齐
start默认值,文本对齐界线开始的地方
end文本对齐界线结束的地方
ctx.strokeStyle = "red"
ctx.moveTo(250, 50)
ctx.lineTo(250, 550)
ctx.stroke();

ctx.font = '48px Microsoft YaHei'

// 处理位置
const textAlign = ['left','right','center','start','end']
for ( var i = 0; i < textAlign.length; i++) {
    ctx.textAlign  = textAlign[i];
    ctx.fillText("美", 250, 100*i + 100, 500-250);
}

Canvas

7 measureText 预测文本宽度

ctx.measureText(text); 方法返回一个 TextMetrics 对象,包含关于文本尺寸的信息(例如文本的宽度)

参数说明
text需要测量的文本

TextMetrics 对象

TextMetrics 对象有很多属性,不过大部分都不被支持,只有 width 属性是完全被支持的,好在我们只需要 width 属性

ctx.font = '22px Microsoft YaHei'

// 预测位置
var w = ctx.measureText("hello world").width;
ctx.fillText("'hello world' 文本的宽度为:" + w , 50, 50);

Canvas

六 图像和视频

1 绘图基础

怎么绘制图片

图片本身就是一个矩形,它有自己的左上角 (0,0) 和宽高 ( w,h ),如下图:

Canvas

可以将整图绘制到画布上,也可以将一部分绘制到画布上(需要裁剪了),如下图:

Canvas

比如我们可以从图片的 (sx, sy) 点开始截取宽高为 (sWidth, sHeight) 的一部分绘制到画布上。

如果 sx=0, sy=0 且 sWidth=width, sHeight=height 那么就是整张图片绘制到画布上

画布相关

画布也是一个矩形,它也有自己的宽高,我们把图片绘制到画布的时候必须指定从哪个点 (dx,dy) 开始画,如下图:

Canvas

如果指定的点 dx=0,dy=0 其实就是从屏幕的左上角开始画起

当然,这样就是可能占据画布的大部分空间,也可能会把其它已经在画布上的东西遮住

所以,也可以在画布上指定区域 (dWidth,dHeight),只将图片画到这个区域里,如下图:

Canvas

如果指定了 (dWidth,dHeight) ,因为它可能和 (sWidth,sHeight) 不一样

可能更小,也可能更大,可能更高,也可能更窄,那么就会涉及到图片的缩放问题

Canvas 只有一种缩放规则,那就是填满指定的区域 (dWidth,dHeight)

2 drawImage 绘制图像

ctx.drawImage() 方法将一张图片绘制到画板上

ctx.drawImage(image, dx, dy);

ctx.drawImage(image, dx, dy, dWidth, dHeight);

ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
参数说明
image绘制到画板的图像资源,可以是任何的 canvas 图像源 ( CanvasImageSource),例如:HTMLImageElement,HTMLVideoElement,或者 HTMLCanvasEleme
dx绘制图像时起点的 X 轴位置
dy绘制图像时起点的 Y 轴位置
dWidth在目标画布上绘制图像的宽度。 允许对绘制的图像进行缩放,如果不传递,绘制图像; 如果不说明, 在绘制时图片宽度不会缩放
dHeight在目标画布上绘制图像的高度。 允许对绘制的图像进行缩放。 如果不说明, 在绘制时图片高度不会缩放
sx截取图像时指定起点的 X 坐标
sy截取图像时指定起点的 Y 坐标
sWidth图像截取的宽度
sHeight图像截取的高度

原图 800X620 如下

Canvas

(1) 在画板的指定点绘制整张图片

<canvas id="c1" width="600" height="400"></canvas>

const ctx = c1.getContext('2d')

// 获取图片
const img = new Image()
img.src = '../image/girl.png'
img.onload = function() {
  // 从原点开始画图
  ctx.drawImage(img, 0, 0);
}

Canvas

绘制出来的图片变成600X400了,因为画布的大小为600X400,如果图片的大小超过画布的大小就会被裁剪,补显示出来了

(2) 在画板上指定位置和指定区域绘制整张图片-就是缩放图片为画布的大小

<canvas id="c1" width="600" height="400"></canvas>

const ctx = c1.getContext('2d')

// 获取图片
const img = new Image()
img.src = '../image/girl.png'
img.onload = function() {
  // 从原点开始画图,将图片缩放为画布的大小
  ctx.drawImage(img, 0, 0, 600, 400);
}

Canvas

绘制出来的图片变成600X400了,刚好和画布对齐,但是图片被缩放了

(3) 截取图片的一部分绘制到画板上的指定区域

<canvas id="c1" width="600" height="400"></canvas>

const ctx = c1.getContext('2d')

// 获取图片
const img = new Image()
img.src = '../image/girl.png'
img.onload = function() {
  // 从(200, 0)开始截取400X310的图片放到画布600X400上
  ctx.drawImage(img, 200, 0, 400, 310, 0, 0, 600, 400);
}

Canvas

3 图像来源 CanvasImageSource

CanvasImageSource 是一个辅助类型,它不是一个接口,也没有对象实现它

它只用于描述下面类型的任何一个对象

1. HTMLImageElement    ===> <img>
2. HTMLVideoElement    ===> <video>
3. HTMLCanvasElement   ===> <canvas>
4. CanvasRenderingContext2D    ===> 另一个渲染上下文 ctx
5. ImageBitmap    ===> 位图资源

4 HTMLImageElement

(1) img 创建三种方式*

(1)使用 <img src="..." /> 标签写在 HTML 文档中

(2)使用 JavaScript 脚本 document.createElement("img") 创建出来的

(3)new Image()

(2) 图片资源获取方式*

(1)使用 document.images 获取所有图片集合

(2)使用 document.getElementsByTagName() 获取特定标签下的图片

(3)使用 document.getElementById() 获取指定id上的图片

(3) img src

const ctx = c1.getContext('2d')

let img = document.getElementById("img1");
ctx.drawImage(img, 0, 0);

(4) new Image

const ctx = c1.getContext('2d')

const img = new Image()
img.src = '../image/girl.png'
img.onload = function() {
  ctx.drawImage(img, 0, 0);
}

(5) new Image

const ctx = c1.getContext('2d')

const img = document.createElement("img");
img.src = '../image/girl.png'
img.onload = function() {
  ctx.drawImage(img, 0, 0);
}

(6) 通过 data: url 方式嵌入图像

const ctx = c1.getContext('2d')

const img = document.createElement("img");
img.src = `data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/
ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==`;
img.onload = function() {
  ctx.drawImage(img, 0, 0);
}

5 HTMLVideoElement

(1) video创建两种方式*

(1)使用 <video src="..." /> 标签写在 HTML 文档中

(2)使用 JavaScript 脚本 document.createElement("video") 创建出来的

(2) 绘制视频

const ctx = c1.getContext('2d')

const video = document.createElement("video");
video.src = '../image/girl.mp4'
video.onload = function() {
  ctx.drawImage(video, 0, 0);
}

6 HTMLCanvasElement

Canvas 绘制另一个 Canvas

// 1 创建一个canvas,填充内容
const canvas1 = document.createElement('canvas')
const ctx = canvas1.getContext('2d')
canvas1.width = 300
canvas1.height = 50
ctx.fillStyle = '#eeeeee'
ctx.fillRect(0, 0, canvas1.width, canvas1.height)
ctx.font = '24px Microsoft YaHei'
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
ctx.fillStyle = '#333333'
ctx.fillText('hello world', canvas1.width / 2, canvas1.height / 2)

// 2 用drawImage将创建的canvas画到画布上
const canvas2 = document.getElementById('c1')
const ctx2 = canvas2.getContext('2d')
ctx2.fillStyle = 'green'
ctx2.fillRect(0, 0, canvas2.width, canvas2.height)
ctx2.drawImage(
  canvas1,
  (canvas2.width - canvas1.width) / 2,
  (canvas2.height - canvas1.height) / 2,
  canvas1.width,
  canvas1.height,
)

Canvas

7 ImageBitmap

(1) ImageBitmap是什么*

(1)ImageBitmap 是一个高性能的位图,可以低延迟地绘

(2)ImageBitmap 可以从一张图片,比如 png, jpg, gif 中生成,也可以从 <img> , <canvas>, <video> 中生成

(2) createImageBitmap 生成位图

(1)createImageBitmap() 方法是 window 对象的一个方法

(2)createImageBitmap 接受各种不同的图像来源, 并返回一个 Promise

(3)很多浏览器不支持

Canvas

(3) createImageBitmap 使用方法

createImageBitmap(image[, options]).then(function(response) { ... });

createImageBitmap(image, sx, sy, sw, sh[, options]).then(function(response) { ... });
参数说明
image一个图像源
sx裁剪点 x 坐标
sy裁剪点 y 坐标
sw裁剪宽度,值可为负数
sh裁剪高度,值可为负数
options可选,一个字典,由于设置各种选项

(3) image 参数

<img>
SVG <image>
<video>
<canvas>

HTMLImageElement
SVGImageElement
HTMLVideoElement
HTMLCanvasElement

Blob
ImageData
ImageBitmap
OffscreenCanvas

(4) option 参数

参数说明
imageOrientation指示图像是按原样呈现还是垂直翻转,可选的值有: none (默认,不翻转)、flipY
premultiplyAlpha指示位图颜色通道由 alpha 通道预乘,可选的值有:none、premultiply、default (默认)
colorSpaceConversion指示图像是否使用色彩空间转换进行解码,可选值有:none、default (默认)
resizeWidth缩放到新的宽度
resizeHeight缩放到新的高度
resizeQuality指定图像缩放压缩质量,可选的值有:pixelated、low (默认)、medium、high

返回值

返回一个 Promise ,参数 resolve 为一个 ImageBitmap 对象

(5) ImageBitmap

ImageBitmap 具有以下属性和方法

参数说明
height只读,正整数,以 px 为单位的图像高度
width只读,正整数,以 px 为单位的图像的宽度
方法说明
close()只释放 ImageBitmap 所相关联的所有图形资源

(6) 案例

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

// 先裁剪600X400的图片,然后生成位图,再将位图画到画布上
const img = new Image()
img.src = '../image/girl.png'
img.onload = function () {
  Promise.all([createImageBitmap(img, 0, 0, 600, 400)]).then(function (sprites) {
    ctx.drawImage(sprites[0], 0, 0, sprites[0].width, sprites[0].height)
  })
}

Canvas

七 变形

1 几何变换

(1) 平移

平移 (translate) 就是将一个图形往水平方向和垂直方向移动一定的距离 (dx, dy)

比如原先的位置 (x, y)

Canvas

平移(dx, dy) 后新的位置为 (x+dx, y+dy)

Canvas

(2) 缩放

缩放 (scale) 就是将一个图形围绕中心点,然后将宽和高分别乘以一定的因子(sx, sy)

比如原先的图形,宽高分别为 (width, height)

Canvas

当水平和垂直方向都乘以一定的缩放因子 (sx, sy) 后,实际的宽度和高度就变为(width _ sx, height _ sy)

Canvas

如果缩放因子为 1.0,那么就没有缩放,如果缩放因子大于 1.0 那么就是放大,如果小于 1.0 就是缩小了

(3) 旋转

旋转 ( rotate ) 将图形围绕一个中心点,顺时针或者逆时针旋转一定的弧度 ( angle )

比如原先的图形

Canvas

旋转一定弧度后 ( angle ) 就是下面这样子

Canvas

如果 angle 值大于 0 则顺时针旋转

如果 angle 值小于 0 则逆时针旋转

(4) 变形

变形 ( Transforms ) 就是将前三种几何融合到一起

比如原先的图形

Canvas

通过变形 ( sx, lx, ly, sy, dy, dx ) 后变成了

Canvas

2 canvas 几何变化

Canvas 几何变换的不是图形,而至整个画布

比如旋转,一开始的画布可能是这样的

Canvas

当它按顺时针旋转一定角度 ( angle ) 后就变成这样了

Canvas

虽然从显示结果来看变换的是图形,但实际上变化的是整个画板,包括坐标体系

3 translate 平移

(1) 平移原理

平移 (translate) 就是将一个图形往水平方向和垂直方向移动一定的距离 (dx,dy)

但是,Canvas 中的所有几何变换针对的不是绘制的图形,而是针对画布本身

例如刚开始的时候在 (50,50) 绘制一个 100x50 的矩形可能是这样的

Canvas

灰色的底为画布(canvas),绿色的边框为屏幕

当我们使用 translate(50,50) 将画布水平和垂直方向各移动 50

然后在 (50,50) 绘制一个 100x50 的矩形就是这样的了

Canvas

此时矩形的起点距离屏幕左上角就已经是 (100,100) 了

(2) 平移实现

ctx.translate() 方法将将 canvas 和 canvas 原点往水平和垂直方向各移动一定的距离

// 没有返回值
void ctx.translate(dx, dy)
参数说明
dx水平方向的移动距离
dy垂直方向的移动距离

(3) 具体案例

先在点 (50,50) 绘制一个橘黄色的 100x50 的矩形,然后平移 canvas (50,50) 最后在点 (50,50) 绘制一个 100x50 的绿色矩形

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

ctx.fillStyle = 'orange'
ctx.fillRect(50, 50, 100, 50)

ctx.translate(50, 50)
ctx.fillStyle = 'green'
ctx.fillRect(50, 50, 100, 50)

Canvas

4 scale 缩放

(1) 缩放原理

缩放 (scale) 就是将一个图形围绕中心点,然后将宽和高分别乘以一定的因子(sx,sy)

但是,Canvas 中的缩放 (scale) 针对的不是绘制的图形,而是针对画布本身

画布缩放了,那么画在画布上的图形自然也就缩放了

例如刚开始的时候在 (100,100) 绘制一个 50x50 的矩形可能是这样的

Canvas

灰色的底为画布(canvas),绿色的边框为屏幕

当我们使用 scale(0.5, 2) 将画布水平方向缩小一倍和垂直方向放大一倍的时候

就是原来水平方向的 1 个单位现在变成了 0.5 个单位了,垂直方向的 1 个单位现在变成了 2 个单位了

然后在 (100,100) 绘制一个 50x50 的矩形就是这样的了

Canvas

  • 首先矩形的大小不变,只是看起来变成了竖直的长方形了,而且 高/宽 的比例看起来就是 4/1
  • 矩形的左上角距离画布的原点(0,0) 的实际实际距离没变,但看起来其实是垂直方向移动了一倍距离而水平方向缩小了一半距离
  • 最大的直观感受就是矩形的左上角距离屏幕的左上角位置变化了,水平方向缩小了一半距离,垂直方向扩大了一半距离

(2) 缩放实现

ctx.scale() 方法将将 canvas 水平方向和垂直方向的单位各乘以一定的因子(sx, sy),从某些方面说, scale() 缩放的不是画布,而是画布上 1 个单位的距离

// 没有返回值
void ctx.scale(sx, sy);
参数说明
sx水平方向的缩放因子
sy垂直方向的缩放因子

dx 和 dy 的值可以是负数,负数是啥意思呢?就是想把整个 canvas 镜像,然后再缩放

(3) 具体案例

先在点 (100, 100) 绘制一个橘黄色的 50x50 的正方形,然后缩放画布 canvas (0.5,2) 最后在点 (100, 100) 绘制一个 50x50 的绿色正方形

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

ctx.fillStyle = 'orange'
ctx.fillRect(100, 100, 50, 50)

ctx.scale(0.5, 2)
ctx.fillStyle = 'green'
ctx.fillRect(100, 100, 50, 50)

Canvas

5 rotate 旋转

(1) 旋转原理

旋转 ( rotate ) 就是将图形围绕一个中心点(0,0),顺时针或者逆时针旋转一定的弧度 ( angle )

但是,Canvas 中的旋转 ( rotate ) 针对的不是绘制的图形,而是针对画布本身

画布旋转了,带来最直接的结果是什么呢? 就是某个点与屏幕上边之间的夹角改变了

我们看一个正常情况下的笛卡尔坐标体系,某个点(50,50) 与屏幕上边和 X 轴正方向之间的夹角都是 45 度

Canvas

灰色的底为画布(canvas),绿色的边框为屏幕

我们画布顺时针旋转 30 度后,就变成了下图这样了

Canvas

还是那个点,点与画布之间的夹角还是 45°,但点与屏幕间的夹角已经变成了 75°

点还往下偏移了一点

(2) 旋转实现

ctx.rotate() 将 canvas 围绕屏幕左上角(0,0) 顺时针旋转一定角度

角度变量表示一个顺时针旋转角度并且用弧度表示

注意: 是顺时针,也就是,顺时针就是往下旋转,为什么没有逆时针呢? 用 360° - 逆时针角度 = 顺时针角度了

// 没有返回值
void ctx.rotate(angle);
参数说明
angle顺时针旋转的弧度。可以通过角度值计算:degree * Math.PI / 180

(3) 弧度 angle 参数

如果我们要顺时针旋转 30°,那么

angle = 30 * Math.PI / 180

如果想要逆时针旋转 30° 怎么办呢?逆时针旋转 30°,其实就是顺时针旋转 -30°,也就是

angle = 360 + ( -30 * Math.PI / 180 )

具体如下图:

Canvas

(4) 具体案例

先在点 (100, 100) 绘制一个橘黄色的 100x50 的矩形,然后顺时针旋转画布 30°,最后在点 (100, 100) 绘制一个 100x50 的绿色矩形

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

ctx.fillStyle = 'orange'
ctx.fillRect(100, 100, 100, 50)

ctx.rotate(30 * Math.PI / 180);
ctx.fillStyle = 'green'
ctx.fillRect(100, 100, 100, 50)

Canvas

6 transform 变形

(1) 变形实现

ctx.transform() 可以一次给 Canvas 添加多种变换,我们可以使用它缩放、旋转、移动和倾斜画布

// 没有返回值
void ctx.transform(a, b, c, d, e, f);
参数说明
a水平缩放
b水平倾斜
c垂直倾斜
d垂直缩放
e水平移动
f垂直移动

如果任意一个参数是无限大(Infinity),变形矩阵也必须被标记为无限大,否则会抛出异常

参数矩阵如下:

Canvas

(2) 具体案例

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

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)

Canvas

八 进阶部分

1 保存和恢复

(1) Canvas 图层

Canvas 为我们提供了 图层(Layer) 的支持,Layer(图层) 是按 "栈结构" 来进行管理的

Canvas

当我们调用 save() 方法,会保存当前 Canvas 的状态然后作为一个 Layer(图层),添加到 Canvas栈 中,

而当我们调用 restore() 方法的时候,会恢复之前 Canvas 的状态,而此时 Canvas 的图层栈 会弹出栈顶的那个 Layer,后继的 Layer 来到栈顶,此时的 Canvas 回复到此栈顶时保存的 Canvas 状态

简单说就是 save() 往栈压入一个 Layer,restore()弹出栈顶的一个Layer,这个 Layer 代表Canvas的 状态!

也就是说可以 save() 多次,也可以 restore() 多次,但是 restore() 的调用次数 不能大于 save() 否则会引发错误

(2) 绘制状态

(1) 当前的变换矩阵

(2) 当前的剪切区域

(3) 当前的虚线列表

(4) 以下属性当前的值

strokeStyle
fillStyle
globalAlpha
lineWidth
lineCap
lineJoin
miterLimit
lineDashOffset
shadowOffsetX
shadowOffsetY
shadowBlur
shadowColor
globalCompositeOperation
font
textAlign
textBaseline
direction
imageSmoothingEnabled

(3) save 方法

ctx.save() 方法用于将画布的当前状态保存到栈中

void ctx.save();

(4) restore 方法

ctx.restore() 方法用于将画布恢复到最近一次的保存状态

如果没有保存状态,此方法不做任何改变

void ctx.restore();

2 合成

(1) 图片混排模式

混排模式可以简单的理解为两个图层按照不同模式,可以组合成不同的结果显示出来,如下图:

Canvas

混排模式会用到两个图层:先绘制的图是目标图(DST) ,后绘制的图是源图(SRC)

Canvas 提供了 26 种图片混排模式

source-over
source-in
source-out
source-atop
destination-over
destination-in
destination-out
destination-atop
lighter
copy
xor
multiply
screen
overlay
darken
lighten
color-dodge
color-burn
hard-light
soft-light
difference
exclusion
hue
saturation
color
luminosity

(2) globalCompositeOperation属性

ctx.globalCompositeOperation 属性用于设定绘制新图形时采用的图形混排模式,它的值必须是上面 26 种之一

ctx.globalCompositeOperation = type;

具体26种取值效果如下:

CanvasCanvasCanvasCanvasCanvasCanvasCanvasCanvasCanvas

(3) 具体案例

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

const gco = [
  'source-over',
  'source-in',
  'source-out',
  'source-atop',
  'destination-over',
  'destination-in',
  'destination-out',
  'destination-atop',
  'lighter',
  'copy',
  'xor',
  'multiply',
  'screen',
  'overlay',
  'darken',
  'lighten',
  'color-dodge',
  'color-burn',
  'hard-light',
  'soft-light',
  'difference',
  'exclusion',
  'hue',
  'saturation',
  'color',
  'luminosity',
]

function draw() {
  for (var i = 0; i < gco.length; i++) {
    ctx.font = '16px Microsoft YaHei'
    ctx.textBaseline = 'middle'
    ctx.fillStyle = '#333'

    ctx.save()
    ctx.fillText(gco[i], 10, 15)
    ctx.fillStyle = 'blue'
    ctx.fillRect(10, 35, 50, 50)
    ctx.translate(70, 0)
    ctx.fillStyle = 'blue'
    ctx.fillRect(10, 35, 50, 50)
    ctx.fillStyle = 'red'
    ctx.fillRect(25, 50, 50, 50)

    const canvas2 = document.createElement('canvas')
    ctx2 = canvas2.getContext('2d')
    ctx2.fillStyle = 'blue'
    ctx2.fillRect(10, 35, 50, 50)
    ctx2.globalCompositeOperation = gco[i]
    ctx2.fillStyle = 'red'
    ctx2.fillRect(25, 50, 50, 50)
    ctx2.globalCompositeOperation = 'source-over'

    ctx.drawImage(canvas2, 85, 0)

    ctx.restore()
    ctx.translate(0, 100)
  }
}

draw()

Canvas

3 裁剪

(1) 理论介绍

Canvas 中的裁剪针对的是画布本身

Canvas 中的裁剪只有一种方法,就是根据路径来裁剪

比如我们有一个画布,画布上有一张图片和一个五角星的路径,如下图:

Canvas

现在我们只想显示五角星里的图片,那么在 Canvas 裁剪就会得到

Canvas

为什么五角都有白色区域,那是因为我们图片不够大,露出了屏幕了

(2) clip 方法

ctx.clip() 方法将当前路径设置为剪切路径

void ctx.clip();
void ctx.clip(fillRule);
void ctx.clip(path, fillRule);
参数说明
fillRule裁剪算法,用于设置判断一个点是在路径内还是在路径外,可选的值有:(1)"nonzero": 非零环绕原则,默认的原则;(2)"evenodd": 奇偶环绕原则
path需要剪切的 Path2D 路径

(3) 具体案例

4 基础动画

4.1 画出动画中的一帧的基本步骤

(1)清空 canvas

只要接下来的内容没有完全充满 canvas (例如背景图),就要需要清空所有

ctx.clear(0,0,canvas.width,canvas.height)

(2)保存 canvas 状态

如果接下来要改变 canvas 状态的设置(样式,变形之类的), 又要在每画一帧之时都是原始状态的话,需要先保存一下

ctx.save()

(3)绘制动画图形 ( animated shapes )

这才是动画帧的重绘

(4)恢复 canvas 状态

如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧

4.2 定时重绘

在 canvas 上绘制动画的每一帧调用的都是 canvas 提供的或者自定义的方法

但是它只提供了绘制动画的每一帧,要让动画动起来,那么就要定时的重绘了

定时能力就不是 canvas 的能力范围了,只能靠windows对象了

比如说 setInterval 、 setTimeout 和 requestAnimationFrame

这三个方法: window.setInterval() 、window.setTimeout() 和 window.requestAnimationFrame() 都可以定时的回调第一个方法

但它们之间是有差别的

(1)window.setInterval()

一定设定,window.setInterval() 就会一直执行下去,除非程序销毁或者主动调用 clearInterval() 取消定时执行

(2)window.setTimeout()

这个应该叫做延时执行更好理解些,当然如果延时执行的回调方法又调用它设置下一个延时

那么效果就和 window.setInterval() 差不多了

同样的,如果不想执行了,可以调用 clearTimeout() 取消执行

(3)requestAnimationFrame(callback)

window.requestAnimationFrame() 方法告诉浏览器希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画

该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用

4.3 window.setInterval()

使用 window.setInterval() 实现一个模拟太阳系的动画吧,先看看代码和效果

<canvas id="c1" width="300" height="300"></canvas>

const c1 = document.getElementById('c1')
const ctx = c1.getContext('2d')

const sun = new Image()
const moon = new Image()
const earth = new Image()

function init() {
  sun.src = '../image/canvas_animation_sun.png'
  moon.src = '../image/canvas_animation_moon.png'
  earth.src = '../image/canvas_animation_earth.png'

  // 设置动画为 60 帧,即1秒( = 1000毫秒)绘制60帧
  setInterval(function () {
    draw()
  }, 1000 / 60)
}

function draw() {
  // 设置图形混合模式
  ctx.globalCompositeOperation = 'destination-over'
  ctx.clearRect(0, 0, 300, 300) // clear canvas

  ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'
  ctx.strokeStyle = 'rgba(0, 153, 255, 0.4)'
  ctx.save()
  ctx.translate(150, 150)

  // Earth
  var time = new Date()
  // 绘制旋转的角度,(2*Math.PI)/6) 其实就是 360/6=60°
  // (2*Math.PI)/6000) 其实就是 360/6000=0.06°
  ctx.rotate(((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds())
  ctx.translate(105, 0)
  ctx.fillRect(0, -12, 50, 24) // Shadow
  ctx.drawImage(earth, -12, -12)

  // Moon
  ctx.save()
  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.restore()

  ctx.beginPath()
  ctx.arc(150, 150, 105, 0, Math.PI * 2, false) // Earth orbit
  ctx.stroke()

  ctx.drawImage(sun, 0, 0, 300, 300)

  window.requestAnimationFrame(draw)
}

init()

4.4 window.requestAnimationFrame()

window.requestAnimationFrame() 的用法与 setTimeout() 很相似,只是不需要设置时间间隔而已

requestAnimationFrame() 会定时的执行一个回调函数,这个时间大概是 60次/s

也就是说大概每秒 60 帧这样子

为什么是 60 ,因为主流的液晶显示器的刷新频率就是 60Hz

window.requestAnimationFrame(callback);
参数说明
callback回调函数,这个回调函数只有一个参数,DOMHighResTimeStamp,指示 requestAnimationFrame() 开始触发回调函数的当前时间 ( performance.now() 返回的时间)

返回值

一个 long 整数,请求 id ,是回调列表中唯一的标识

我们可以通过把这个 id 传递给 window.cancelAnimationFrame() 以取消回调函数

4.5 window.cancelAnimationFrame()

window.cancelAnimationFrame() 函数用于取消 window.requestAnimationFrame()方法添加到计划中的动画帧请求

window.cancelAnimationFrame()(requestID); 
参数说明
requestID调用 window.requestAnimationFrame() 方法时返回的 ID

返回值

一个 long 整数,请求 id ,是回调列表中唯一的标识

我们可以通过把这个 id 传递给 window.cancelAnimationFrame() 以取消回调函数

5 事件

5.1 键盘事件

属性说明
onkeydown当按下按键时运行脚本
onkeypress当按下并松开按键时运行脚本
onkeyup当松开按键时运行脚本

5.2 鼠标事件

属性说明
onclick当单击鼠标时运行脚本
ondblclick当双击鼠标时运行脚本
ondrag当拖动元素时运行脚本
ondragend当拖动操作结束时运行脚本
ondragenter当元素被拖动至有效的拖放目标时运行脚本
ondragleave当元素离开有效拖放目标时运行脚本
ondragover当元素被拖动至有效拖放目标上方时运行脚本
ondragstart当拖动操作开始时运行脚本
ondrop当被拖动元素正在被拖放时运行脚本
onmousedown当按下鼠标按钮时运行脚本
onmousemove当鼠标指针移动时运行脚本
onmouseout当鼠标指针移出元素时运行脚本
onmouseover当鼠标指针移至元素之上时运行脚本
onmouseup当松开鼠标按钮时运行脚本
onmousewheel当转动鼠标滚轮时运行脚本
onscroll当滚动元素的滚动条时运行脚本

6 像素操作

6.1 ImageData 对象

ImageData 对象中存储着 canvas 对象真实的像素数据,ImageData属性如下:

属性说明
ImageData.data只读,Uint8ClampedArray 类型的一维数组,包含着 RGBA 格式的整型数据,范围在 0 至 255 之间( 包括 255 )顺序的数据
ImageData.height只读,无符号长整型(unsigned long),图片高度,单位是像素
ImageData.width只读,无符号长整型(unsigned long),图片宽度,单位是像素

6.2 Uint8ClampedArray

Uint8ClampedArray 是一个 height × width × 4 bytes 的一维数组,索引值从 0 到 (height× width × 4)-1

图片是平面图形,有宽有高,因此我们可以把它看作是一个矩形,如果我们以 1 像素为单位给它绘制一个网格,下面这样:

Canvas

小格子一共有(width / 1 ) x ( height/1 ) = width x height 个

每一个像素格子都是一种颜色,那么总共就有 width x height 个颜色

Canvas 使用的是 RGBA 存储一个颜色值,也即是一个颜色需要 4 字节来存储

Canvas 中存储一个张 width x height 的图片就需要 高度 × 宽度 × 4 bytes 字节了

既然知道了 Uint8ClampedArray 是一个 高度 × 宽度 × 4 bytes 的一维数组

那么它的索引值就是从 0 到 (高度×宽度×4) -1

那么 Uint8ClampedArray 到底是是怎么存放每个像素的颜色呢?

很简单,首先创建一个长度为 (高度×宽度×4) 的字节数组,然后按照下面的公式存放第 m 行 n 列颜色(RGBA)

R = 4 * (m-1) * (n-1) + 0
G = 4 * (m-1) * (n-1) + 1
B = 4 * (m-1) * (n-1) + 2
A = 4 * (m-1) * (n-1) + 3

例如第一行第一列的颜色放在

R = 4 * 0 * 0 + 0 = 0
G = 4 * 0 * 0 + 1 = 1
B = 4 * 0 * 0 + 2 = 2
A = 4 * 0 * 0 + 3 = 3

第一行第二列的颜色放在

R = 4 * 1 * 1 + 0  = 4
G = 4 * 1 * 1 + 1  = 5
B = 4 * 1 * 1 + 2  = 6
A = 4 * 1 * 1 + 3  = 7

详细如下图:

Canvas

所以我们要取得第 m 行第 n 列颜色值的 B 值,就可以像下面这样使用:

ImageData.data[(m-1)*(n-1)*4+3]

例如从行 50,列 30 的像素中读取图片的蓝色部份,就会这样写:

blue = ImageData.data[(50-1)*(30-1)*4+3] = ImageData.data[7647]

6.3 获取 Uint8ClampedArray 的长度

既然 Uint8ClampedArray 是一个数组,我们就可以使用 length 属性来获得数组的长度

const len = imageData.data.length

6.4 ImageData 构造函数

new ImageData(array, width[, height]);
new ImageData(width, height);
参数说明
array一个是 Uint8ClampedArray,就是一个装满了颜色的一维数组
widthImageData 所表示的图像的宽度
heightImageData 所表示的图像的宽度

(1)如果使用三个参数的构造方法,那么还有一个约束条件,就是必须满足 Uint8ClampedArray 的 length 值等于 4 * width * height ,否则会报错

(2)如果使用两个参数的构造方法,那么会自动创建一个 4 * width * height 大小的一维数组,然后以 0 填充整个像素矩阵,全部都是 0 是啥意思? #000000 知道是啥颜色吧,对,全部是 0 就表示一个透明黑的图片,透明黑是啥? 就是透明的图像啦

(3)如果传递了 array 参数,那么 height 参数可以选择不传递,如果不传递,那么它会自己计算

height = array.length / ( width * 4 )

案例

创建一个 1 x 1 大小的纯黑色的图像数据

const imageData = new ImageData(1, 1);

console.log(new ImageData(1, 1));

Canvas