# Canvas 101
# Canvas
Thecanvas
element provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics, art, or other visual images on the fly.
- https://html.spec.whatwg.org/multipage/canvas.html#the-canvas-element
Canvas是HTML5新增的组件,它就像一块幕布,可以用JavaScript在上面绘制各种图表、动画等。
没有Canvas的年代,绘图只能借助Flash插件实现,页面不得不用JavaScript和Flash进行交互。有了Canvas,我们就再也不需要Flash了,直接使用JavaScript完成绘制。
# Canvas能做些什么
- 地图: https://map.baidu.com/ https://www.google.com.au/maps
- 图表:https://ecomfe.github.io/echarts-examples/public/index.html
- 在线、离线游戏
- 动画
- 有交互的视频、音频
- More …
# Canvas Start
<canvas>
元素
1.创建空白画布,设置宽高
<canvas id="root" width="150" height=“150"></canvas>
没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素 元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容
渲染上下文:
- 2D
- 3D => WebGL
2.获取渲染上下文
<canvas>
元素有一个叫做getContext()
的方法,这个方法是用来获得渲染上下文和它的绘画功能。
var canvas = document.getElementById('root');
var ctx2d = canvas.getContext(‘2d’); => CanvasRenderingContext2D
var ctx2d = canvas.getContext(‘webgl’);
3.检查支持性
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
}
# Canvas Start - 栅格/坐标系
栅格 canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角,坐标为(0,0)
# Canvas Start - 填充 VS 描边
2D上下文中基本绘图操作是填充和描边
- 填充: 使用指定的样式(颜色、渐变或图像)填充图形
- 描边: 在图形的边缘画线
# Canvas Draw
# 绘制实体矩形
- fillRect(x, y, width, height) 绘制一个填充的矩形
- strokeRect(x, y, width, height) 绘制一个矩形的边框
- clearRect(x, y, width, height) 清除指定矩形区域,让清除部分完全透明
eg:
function drawRectangle() {
const canvasNode = document.querySelector('.rectangle');
if(canvasNode.getContext) {
const ctx = canvasNode.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 150, 100);
ctx.strokeStyle = 'blue';
ctx.strokeRect(200, 200, 150, 100);
}
}
# 绘制路径
绘制路径 图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。 一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤:
- 首先,你需要创建路径起始点
- 然后你使用画图命令去画出路径
- 把路径封闭
- 一旦路径生成,你就能通过描边或填充路径区域来渲染图形
绘制路径函数
- beginPath() 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径
- closePath() 闭合图形路径
- stroke() 通过线条来绘制图形轮廓(不自动闭合)
- fill() 通过填充路径的内容区域生成实心的图形(自动闭合)
- lineTo(x, y) 从上一点开始绘制一条直线,到(x, y)为止.
- moveTo(x, y) 将绘图游标移动到(x, y),不画线
- 其他:画弧线,曲线,二次曲线,矩形
eg:
function drawPath() {
const canvasNode = document.querySelector('.path');
if(canvasNode.getContext) {
const ctx = canvasNode.getContext('2d');
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 25);
ctx.lineTo(200, 175);
ctx.fill();
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 150, 100);
ctx.beginPath();
ctx.moveTo(300, 300);
ctx.lineTo(400, 125);
ctx.lineTo(400, 375);
}
}
# 绘制文本
文本
- fillText(string, x, y) 要绘制的文本字符串,x坐标,y坐标和可选的最大像素值
- strokeText(string, x, y) 要绘制的文本字符串,x坐标,y坐标和可选的最大像素值
以上的两个方法以以下的三个属性为基础
- context.font
- context.textAlign //对齐方式
- context.textBaseline //基线
eg:
function drawText() {
const canvasNode = document.querySelector('.text');
if(canvasNode.getContext) {
const ctx = canvasNode.getContext('2d');
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.stroke();
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'left'; //end center left right
ctx.textBaseline = 'bottom'; //bottom
ctx.fillText('12', 150, 100);
ctx.strokeText('12', 250, 200);
}
}
# Canvas 状态管理
save()
和restore()
save
和restore
方法是用来保存和恢复canvas
状态的,都没有参数。
Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。 Canvas状态存储在栈中,每当save方法被调用后,当前的状态就被推送到栈中保存。
一个绘画状态包括:
- 当前应用的变形(即移动,旋转和缩放…)
- strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor 等值
可以调用任意多次 save 方法。
每一次调用restore
方法,上一个保存的状态就从栈中弹出,所有设定都恢复。
eg:
function drawState() {
const canvasNode = document.querySelector('.state');
if(canvasNode.getContext) {
const ctx = canvasNode.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 Transform 变换
- rotate(angle): 围绕原点旋转图像angle弧度,顺时针旋转
- scale(scaleX, scaleY): 缩放图像,在x方向乘以scaleX,在y方向乘以scaleY,默认值都是1.0
- translate(x, y): 将坐标原点移动到(x,y) 执行这个变换之后,坐标(0,0)会变成之前由(x,y)表示的点
- transform(m1_1, m1_2, m2_1, m2_2, dx, dy) 直接修改变换矩阵,方式是乘以一个其他矩阵
- setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy) 将变换矩阵重置为默认状态,然后在调用transform()
eg:
function drawTransform() {
const canvasNode = document.querySelector('.transform');
if(canvasNode.getContext) {
const ctx = canvasNode.getContext('2d');
ctx.translate(75, 75);
for(let i = 1; i < 6; i++) {
ctx.fillStyle = 'rgb(' + (51 * i) + ',' + (255 - 51 * i) + ',255)';
for(let 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();
}
}
}
}
# Canvas Image 图像
可以用于动态的图像合成或者作为图形的背景,以及游戏界面(Sprites)等等。浏览器支持的任意格式的外部图片都可以使用,比如PNG、GIF或者JPEG。 你甚至可以将同一个页面中其他canvas元素生成的图片作为图片源
步骤:
1.获得一个指向HTMLImageElement的对象
2.使用drawImage()函数将图片绘制到画布上
- drawImage(image, x, y);
- drawImage(image, x, y, width, height); //目标图像的宽度 目标图像的高度
- drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) //将图片的一部分绘制
eg:
function drawImage() {
const canvasNode = document.querySelector('.image');
if(canvasNode.getContext) {
const ctx = canvasNode.getContext('2d');
const img = new Image(); // 创建img元素
img.onload = function() {
// ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 0, 0, 600, 100);
};
img.src = './snow.jpg'; // 设置图片源地址
}
}
# Canvas Other
- 阴影
- 渐变
- 合成
- 像素操作
- More…
# Canvas 动画
基本步骤:
1.清空canvas
除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法
2.保存 canvas 状态
如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下
3.绘制动画图形
这一步是重绘动画帧
4.恢复canvas
状态
如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧
- setInterval(function, delay)
- setTimeout(function, delay)
- requestAnimationFrame(callback)
eg:
function drawAnimation() {
const canvasNode = document.querySelector('.animation');
if(canvasNode.getContext) {
const ctx = canvasNode.getContext('2d');
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.stroke();
ctx.font = 'bold 30px Arial';
ctx.textAlign = 'center'; //end center left right
ctx.textBaseline = 'top'; //bottom
ctx.fillText('12', 150, 100);
}
}