Canvas 技术涉及到 JS 知识,请先参阅 《JavaScript 技术详解》 博文。
- 参考文档:Mozilla 提供的: Canvas 教程(中文)
- 介绍:
是一个可以使用脚本 (通常为
JavaScript
) 来绘制图形的 HTML 标签。- 应用:它可以用于
绘制图表
、制作图片构图
、制作动画
。- 注意:
的默认大小为 300像素×150像素(宽×高,像素的单位是px),可以使用 高度 和 宽度 属性来自定义Canvas 的尺寸。
- 尽量不要使用 css 去控制图像的大小,因为如果 css 定义的大小和 画布的初始大小不一致时,图像会去适应画布大小,导致图像扭曲。直接设置画布 width 和 height 属性。
- 当开始时没有为 canvas 规定 样式规则,其将会完全透明。
看起来和
元素很相像,唯一的不同就是它并没有 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>
标签创造了一个固定大小的
画布
,它公开了一个或多个渲染上下文
,其可以用来 绘制 和 处理 要展示的内容。
我们将会将注意力放在2D 渲染上下文
,即CanvasRenderingContext2D
对象。
获取 2D 渲染上下文 对象:
// 获取画布元素
var canvas = document.getElementById('tutorial');
// 检查浏览器对 canvas 的支持性
if (canvas.getContext){
// 并获取 2D 渲染上下文 对象
var ctx = canvas.getContext('2d');
// 绘制代码...
} else {
// 不支持 Canvas
}
<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>
画布栅格
:栅格的起点为左上角,坐标为(0, 0)。坐标空间
:所有 元素的位置 都相对于 原点定位。
- 不同于 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);
}
}
图形的基本元素是路径。使用 路径 绘制图形 的步骤:
- 创建路径
起始点
。- 使用
画图命令
去画出路径。- 把路径
封闭
。- 一旦路径生成,你就能通过
描边
或填充
路径区域 来渲染图形。
绘制路径 所要用到的函数:
- 生成路径:
a.beginPath()
:新建一条路径,生成之后,图形绘制命令 被指向到路径上 生成路径。
每次这个方法调用之后,画布会清空重置
。
第一条 路径构造 命令通常 是 moveTo()。
b.moveTo(x, y) 非必须
:将笔触(就是新的起点
的意思)移动到指定的坐标 (x,y) 上。简单点理解,就是换一个地方开始绘制,断开之前连续的路径。
c.closePath() 非必须
:闭合路径之后,图形绘制命令 又重新指向到 上下文中。
使用场景:仅仅在使用线条绘制 stroke() ,并且终点不在起点时,才需要 closePath()。
- 中间是各种
子路径
,包括 线、弧形 等等,下面一节会介绍具体介绍 各种 子路径 函数
。- 路径构建完毕后,开始绘制图形,包括 线条,和填充 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();
}
}
lineTo(x, y)
:构建 直线 路径。绘制一条从当前位置到指定(x, y)位置的直线。arc(x, y, radius, startAngle, endAngle, anticlockwise)
:构建 圆弧 或 圆 路径。弧度起点为 x轴正方向
。
1PI 弧度 对应 180度
。false
,表示顺时针方向开始画。rect(x, y, width, height)
:构建矩形路径。区别于 fillRect 等三个直接矩形绘制方法,这只是画出路径
。
quadraticCurveTo(cp1x, cp1y, x, y)
:绘制 二次贝塞尔曲线。bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
:绘制 三次贝塞尔曲线。
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();
}
}
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();
}
}
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();
}
Path2D 对象
是用来缓存
或 记录绘画命令
,达到一次构建,多次使用
的效果。所有的路径方法
,如moveTo
,rect
,arc
,quadraticCurveTo
等,都可以在Path2D
对象中使用。- 它的出现是为了
简化代码
和提高性能
。Path2D 对象已可以在较新版本的浏览器中使用。
new Path2D()
:返回一个新初始化的 Path2D 对象,空的。new Path2D(path)
:可以添加路径 path。new Path2D(d)
:可以添加 SVG path
数据的字符串,来初始化 canvas 上的路径。addPath(path)
:添加了一条路径到当前路径,不常用。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);
}
}
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);
}
}
- 色彩属性:
fillStyle
,strokeStyle
,分别 设置图形的填充
颜色,和轮廓
颜色。- 属性值 可以是表示
CSS 颜色值的字符串
:"orange"
,"#FFA500"
,"rgb(255,165,0)"
,"rgba(255,165,0,1)"
。- 也可以是 渐变对象
canvasGradient
,背景图案对象pattern
。默认情况下
,线条 和 填充颜色都是黑色
,#000000
。- CSS3 颜色值标准
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);
}
}
}
}
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();
}
}
}
}
- 透明度设置 可以 通过设置
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)";
通过一系列
属性
和方法
来设置线的样式
。
线型相关 属性 和 方法:
属性 | 意义 | 属性值 | 示例 |
---|---|---|---|
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() |
可以使用
线性
或径向
的canvasGradient 对象
,赋值给颜色属性
,来实现渐变
描边 或 填充。
使用方法:
需要通过 ctx
创建 canvasGradient 对象
var gradient = ctx.createLinearGradient(x1, y1, x2, y2)
:线性渐变
,接受 4 个参数,表示渐变的起点
和终点
。var gradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2)
:径向渐变
,接受 6 个参数,表示渐变的起点圆
和终点圆
。- 这 2 个对象都可以实现渐变,只是 渐变效果 不同而已。
gradient.addColorStop(position, color)
:接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示 渐变终止 的位置。color 表示渐变终止的 颜色。
ctx.fillStyle = gradient;
ctx.strokeStyle = gradient;
- ctx.fillRect(10,10,130,130);
- ctx.strokeRect(50,50,50,50);
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);
}
}
这里说一下,简单的径向渐变就是,只有一个中心点,简单地由中心点向外围的圆形扩张。
下面的比这要复杂一点,定义了 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);
}
}
可以使用
pattern 对象
,赋值给颜色属性
,来实现图案背景
设置,并可以控制 循环方式。
使用方法:
需要通过 ctx
创建 pattern 对象:
ctx.createPattern(image, type)
:接受两个参数。
image
可以是一个 Image 对象的引用,或者另一个 Canvas 对象( 但在 Firefox 1.5 (Gecko 1.8) 中是无效的)。type
必须是下面的字符串值之一:repeat
,repeat-x
,repeat-y
和no-repeat
。
注意:
必须确保 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);
}
}
}
阴影属性 | 意义 |
---|---|
shadowOffsetX ,shadowOffsetY |
设定阴影在 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);
}
}
注意:在 Geoko(Firefox,Firefox OS 及 基于 Mozilla 的应用的渲染引擎)中,曾有一些版本较早的 API 实现了在 Canvas上对 文本 作画的功能,但它们现在已不再使用。
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);
}
}
属性 | 意义 | 属性值 | 示例 |
---|---|---|---|
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 : 从右向左 |
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;
}
}
- Canvas 更有意思的一项特性就是
图像操作
能力。- 用途:
作为图形的背景
,4.5 背景图案,pattern 对象 小节中有说明。制作 动态的图像
。
图片来源 | 使用方式 | 示例 |
---|---|---|
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 |
同一个页面中 其他 canvas元素 生成的 图片 |
通过 HTMLCanvasElement 对象使用。具体操作: 直接使用另一个 元素作为图片源。 |
1. 使用其它 canvas 元素 :和引用页面内的图片类似地,用 document.getElementsByTagName 或 document.getElementById 方法来获取其它 canvas 元素。但你引入的应该是已经准备好的 canvas。一个常用的应用就是将第二个canvas作为另一个大的 canvas 的缩略图。 |
从 视频 中抓取 当前帧 作为一个图像 | 通过 HTMLVideoElement 对象使用。具体操作: 直接用一个HTML的 元素作为你的图片源。 |
1. 使用视频帧 :使用 中的视频帧(即便视频是不可见的)。例如,如果你有一个ID为 “myvideo” 的 元素:document.getElementById('myvideo'); 它将为这个视频返回 HTMLVideoElement 对象,它可以作为我们的 Canvas 图片源。 |
高性能的位图,以低延迟地绘制 | 通过 ImageBitmap 对象使用。具体操作: ImageBitmap 对象可以从上述的所有源以及其它几种源的 对象 中生成。 |
drawImage(image, x, y)
:放置图片。
x 和 y
: 是其在目标 canvas 里的起始坐标。drawImage(image, x, y, width, height)
:缩放图片。
width 和 height
:控制当向 canvas 画入时,应该缩放的大小。drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
:切片图片。
注意点:
禁止图像的缩放行为
:Gecko 1.9.2 引入了 mozImageSmoothingEnabled 属性,默认是 true 。
值为 false 时,图像不会平滑地缩放。
ctx.mozImageSmoothingEnabled = false;
// 禁止缩放切片
是个做图像合成的强大工具。假设有一张包含了所有元素的图像,那么你可以用这个方法来合成一个完整图像。
// 线性图的背景,除了 折线 外,其余的是背景
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();
};
}
}
// 缩放图片重复平铺
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); // 缩放图片
}
}
};
}
}
- 相框,图像 和 相框 放在 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);
}
}
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);
}
}
}
}
变形是一种更强大的方法,可以将
画布
的原点移动
到另一点,对网格进行缩放
,旋转
。
在了解变形之前,先介绍 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); // 使用加载的配置绘制一个矩形
}
}
注意:
在做变形之前先保存状态是一个良好的习惯
。
如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。
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();
}
}
}
}
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);
}
}
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();
}
}
}
}
transform()
:通过修改变形矩阵
参数,来同时实现缩放
,旋转
,移动
。
- (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);
}
}
变形要点
:在画布中,先确定坐标系,再画图。- 移动,缩放,旋转,都是针对
坐标系
的,而不是 图形 本身。
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 |
保持底层的 色度 和 色调 ,同时采用顶层的 亮度。 |
裁剪
:是指将路径所在的区域
从画布上裁剪
出来,之后所有的 图形 都只会在裁剪区域
出现,其他地方的 图形 会被隐藏
。- 和上面介绍的 globalCompositeOperation 属性作比较的话,它可以实现与
source-in
和source-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();
}
上个示例中,使用了
渲染星星
的算法,可以根据下面的参考图
进行理解。
由于我们是用
JavaScript
去操控对象,这样要实现一些交互动画也是相当容易的。
可能最大的限制就是图像一旦绘制出来,它就是一直保持那样了。如果需要移动它,我们不得不对所有东西(包括之前的)进行重绘
。重绘是相当费时的,而且性能很依赖于电脑的速度
。
1. 清空 canvas
:clearRect()
透明
。2. 保存 canvas 状态
:save()
3. 绘制动画图形
:重绘
动画帧,比如 路径,样式,文本,图像,变形,裁剪
等等。4. 恢复 canvas 状态,继续画下一帧
:restore()
核心思想:在特定的
时间间隔
里,渲染一帧图像
,形成连续的
动画。当 帧率 越高,动画 视觉效果 越好。
控制时间间隔,进行帧的渲染,有 2 种方法:
1 . 使用
定时器
进行控制:帧率可控
setInterval(function, delay)
:定期执行指定代码,循环执行。setTimeout(function, delay)
:定期执行指定代码,执行 1 次。
2 . 使用
重绘方法
进行控制:帧率不可控
- var
raf
= window.requestAnimationFrame(draw)
:告诉浏览器你希望重绘
一个动画。即在下次重绘之前
,会请求浏览器执>行一个特定的函数draw()
来重绘1 次
动画。
所以,需要使用嵌套循环
,实现不间断更新
:通过在 特定函数draw()
中 循环自调用requestAnimationFrame(draw)
来实现 帧 的连续渲染
。
当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟 回调函数 执行60
次,也有可能会被降低。- window.
cancelAnimationFrame(raf)
:取消
重绘 函数,注意参数是重绘函数
的返回值。下一节的高级动画
示例中有用到。
// 创建图片源
// 太阳的图片,包括了太阳 和 星空背景
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();
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);
可以随意找一张任何尺寸大于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;
}
效果应该是连续的,只是制作动态图时,没有截取完整,所以会有卡顿现象:
- 每开始一阶段绘制前,要记得
save
和restore
状态,不要影响到其他的绘制。- 记住
变换的永远都是 画布,也就是 坐标系
,和 图像 没有关系。特别的旋转的时候,不要对坐标系有固化思维
。
上一节的
基本动画
实现了物件移动
的方法。
接下来的高级动画
会添加一些符合物理的运动
,让我们的动画更加高级。
小球运动 案例中 需要实现的
功能
:
- 绘制出一个小球
- 添加长尾效果
- 添加碰撞检测
- 添加移动速度
- 添加加速度
- 添加鼠标控制:默认
鼠标拖动
确定起点,单击
控制开始和暂停,双击
重新确定起点
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();
有时间再补充。
像素的读取和写入
:可以直接通过ImageData
对象 操纵画布
像素 数据。反锯齿
:控制图像,使其平滑。保存图像
:如何从Canvas画布中保存图像。
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 表示需要写入像素数据
的画布区域的左上角
的坐标点。
ImageData 对象中存储着 画布中 真实的
像素数据
,它包含以下几个只读
属性:
width
:图片宽度,单位是像素。height
:图片高度,单位是像素。data
:Uint8ClampedArray 类型的一维数组
,里面是初始像素数据。
- 每个像素用 4 个1 bytes 值( 按照
红
,绿
,蓝
和透明值
的顺序,即 “RGBA” 格式 ) 来代表,范围在0至255之间(包括255)。- 左上角像素的红色部份在数组的索引 0 位置。像素从左到右被处理,然后往下,遍历整个数组。
Uint8ClampedArray
包含高度 × 宽度 × 4
bytes数据- 索引值从
0
到(高度 × 宽度 × 4) - 1
。
根据 行、列 读取
某像素点的 R/G/B/A
值的公式:
imageData.data[((行数-1)*imageData.width + (列数-1))*4 - 1 + 1/2/3/4];
读取
像素数组的 大小
:
// 图像的 像素数组 的长度,即是 图像的大小,单位是 byte
var numBytes = imageData.data.length;
在使用
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>
- 在
img.src
前,必须先设置img.crossOrigin ='anonymous'
以 获取到图像的数据。- 源图像的服务器 应将
access-control-allow-origin
设置为*
或选定的域
。
思路
:遍历所有像素 并 修改,然后我们将被修改的 像素数组 通过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>
- 缩放:通过
drawImage()
的缩放
方法可以实现 图片缩放的效果,并且可以指定 缩放位置
和大小
。
而scale()
函数是 用来缩放画布,并且只是指定缩放的倍数
,不适合 图片的缩放。- 反锯齿:设置
imageSmoothingEnabled = false
属性来关闭
它,以看到清楚的像素
。
- 反锯齿 默认是
启用
的。
示例中的 图片 来源于百度,如果跨域了,可以自行替换。
<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>
HTMLCanvasElement
提供了 2 种,生成图像链接 URL
的方法:toDataURL()
和toBlob()
- 生成的 URL 可以 放在一个有
download
属性的超链接
里,或 用于元素。
使用方法:
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>
canvas.toBlob(callback, type, encoderOptions):
callback
:回调函数,参数是该画布的 Blob 图片对象type
:图片格式,默认格式为 image/png
。encoderOptions
:Number 类型,值在0与1之间。当请求图片格式为 image/jpeg
或 image/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);
元素是众多广泛使用的
网络2D图像
渲染标准之一。它被广泛用于游戏
及 复杂的图像可视化
中。然而,随着网站和应用将 canvas 画布推至极限,性能
开始成为问题。
在 离屏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 设置大的 不要在用 用 关闭 上下文的 将画布的函数 避免不必要的 尽可能避免 尽可能避免 使用不同的办法去清除画布: 有动画,请使用 静态背景图
:
比如像大多数游戏那样,你有一张静态的背景图,用一个静态的 画布元素
的 后面
。画布 默认是透明的。
这么做可以避免在 每一帧
在画布上绘制 背景图。
drawImage
时 缩放
图像:
在 离屏canvas 中缓存图片
的不同尺寸
,而不要用 drawImage()
去缩放
它们。CSS
的 transforms
特性缩放画布:
CSS
transforms 特性由于调用GPU,因此更快捷。
最好的情况是:不要将小画布放大,而是去将 大画布缩小
。
例如:Firefox系统,目标分辨率 480 x 320 pxvar 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
13.3 相关应用教程