《HTML5 Canvas》笔记 - bobby169/createjsDoc GitHub Wiki
《HTML5 Canvas 2nd Edition 》
Canvas状态
在Canvas环境中绘制时,可以利用所谓的绘制堆栈状态。每个状态随时存储Canvas上下文数据。下面是存储在状态堆栈的数据列表。
- 变换矩阵信息。如旋转或平移,使用context.rotate()和context.setTransform()
- 当前剪贴区域。context.clip()
- 画布属性的当前值,如下所示(但不局限于此)
- globalAlpha
- globalCompositeOperation
- strokeStyle
- textAlign, textBaseline
- lineCap, lineJoin, lineWidth, miterLimit
- fillStyle
- font
- shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY
什么不属于状态
当前路径和当前位图受Canvas环境控制,不属于保存的状态。这个重要的功能允许在画布上对单个对象进行绘画和制作动画。2.7节将 初始化Canvas状态,以将变换应用到当前建立和绘制的形状,同时保持画布其他部份不变。
如何保存和恢复Canvas状态
- 保存(推送)当前状态到堆栈,调用
context.save() - 调出存储的堆栈恢复画布,使用
context.restore()
使用路径创建线段
路径可以用来在画布上绘制任何形状。路径就是一系列点及这些点之间的连线。Canvas环境只能有一个当前路径,当调用context.save()方法时,不会将它储存为当前绘图状态的一部份。
设置路径的开始和结束
调用beginPath()开始一个路径,closePath()结束一个路径。连接路径内的两个点的路径称为子路径。如果终点与起点相连,子路径即成为封闭的路径。
function drawScreen() {
//background
context.fillStyle = "#ffffaa";
context.fillRect(0, 0, 500, 300);
//text
context.fillStyle = "#000000";
context.font = "20px Sans-Serif";
context.textBaseline = "top";
context.fillText ("Hello World!", 195, 80 );
//image
var helloWorldImage = new Image();
helloWorldImage.onload = function () {
context.drawImage(helloWorldImage, 155, 110);
}
helloWorldImage.src = "helloworld.gif";
//box
context.strokeStyle = "#000000";
context.strokeRect(5, 5, 490, 290);
}
Canvas裁切区域
使用Canvas裁切区域可以限制路径及其子路径的绘制区域。首先,通过rect()函数定义一个矩形设置为裁切区域。现在,无论在当前环境绘制什么内容,它只显示裁切区域以内的部份。这也可以理解成是绘图操作的一种蒙版。
function drawScreen() {
//draw a big box on the screen
context.fillStyle = "black"; //need list of available colors
context.fillRect(10, 10, 200, 200);
//开始保存context上下文
context.save();
context.beginPath();
//裁切一个50X50的区域,只有这个区域的才显示
// //clip the canvas to a 50x50 square starting at 0,0
context.rect(0, 0, 50, 50);
context.clip();
// //red circle
//在上面50X50的区域显示圆环,所以只显示了一部份
context.beginPath();
context.strokeStyle = "red"; //need list of available colors
context.lineWidth=5;
context.arc(100, 100, 100, (Math.PI/180)*0, (Math.PI/180)*360, false); // full circle
context.stroke();
context.closePath();
//想再次裁剪画布,就要用context.restore(),从堆栈里弹出context
context.restore();
//reclip to the entire canvas
context.beginPath();
//重新裁剪一个500X500的区域,只显示这个区域的内容
context.rect(0, 0, 500, 500);
context.clip();
// //draw a blue line that is not clipped
context.beginPath();
context.strokeStyle = "blue"; //need list of available colors
context.lineWidth=5;
context.arc(100, 100, 50, (Math.PI/180)*0, (Math.PI/180)*360, false); // full circle
context.stroke();
context.closePath();
}
在画布上合成
合成是指如何精细控制画布上对象的透明度和分层效果。有两个属性可以控制Canvas合成操作:
- globalAlpha: (0.0完全透明 ~ 1.0完成不透明),此属性必须在图形绘制之前设置。
- globalCompositeOperation
简单的画布变换
画布变换是指用数学方法调整所绘形状的物理属性。所有变换都依赖于后台的数学矩阵运算。
function drawScreen() {
//画布应用setTransform()函数后对形状起作用。
context.setTransform(1,0,0,1,0,0);
var angleInRadians =45 * Math.PI / 180;
context.rotate(angleInRadians);
//下面的代码不能放到上面,不然没效果
context.fillStyle = "red"; //need list of available colors
context.fillRect(100,100 , 50, 50);
}
上面的context.setTransform(1,0,0,1,0,0)是将Canvas变换设置为identity(或“reset”)矩阵
看一下createjs.Matrix2D中的identity()函数
p.identity = function() {
this.a = this.d = 1;
this.b = this.c = this.tx = this.ty = 0;
return this;
};
围绕中心点旋转
function drawScreen() {
//draw black square
context.fillStyle = "black";
context.fillRect(20,20 , 25, 25);
//now draw a red square
context.setTransform(1,0,0,1,0,0);
var angleInRadians =45 * Math.PI / 180;
var x=100;
var y=100;
var width=50;
var height=50;
//将画布圆点平移到红色正方形中心点,旋转才绕自己中心转
//新建平移点
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.fillStyle = "red";
//基于平移点绘制
context.fillRect(-.5*width,-.5*height , width, height);
}
旋转多个正方形
function drawScreen() {
//now draw a red square
context.setTransform(1,0,0,1,0,0)
var angleInRadians =45 * Math.PI / 180;
var x=50;
var y=100;
var width=40;
var height=40;
context.translate(x+.5*width, y+.5*height)
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
context.setTransform(1,0,0,1,0,0)
var angleInRadians =75 * Math.PI / 180;
var x=100;
var y=100;
var width=40;
var height=40;
context.translate(x+.5*width, y+.5*height)
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
context.setTransform(1,0,0,1,0,0)
var angleInRadians =90 * Math.PI / 180;
var x=150;
var y=100;
var width=40;
var height=40;
context.translate(x+.5*width, y+.5*height)
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
context.setTransform(1,0,0,1,0,0)
var angleInRadians =120 * Math.PI / 180;
var x=200;
var y=100;
var width=40;
var height=40;
context.translate(x+.5*width, y+.5*height)
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
缩放变换
function drawScreen() {
//now draw a red square
context.setTransform(1,0,0,1,0,0);
var x=100;
var y=100;
var width=50;
var height=50;
//缩放操作也要平移原点,从中心点缩放
context.translate(x+.5*width, y+.5*height);
context.scale(2,2);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
缩放和旋转组合
function drawScreen() {
context.setTransform(1,0,0,1,0,0);
var angleInRadians =45 * Math.PI / 180;
var x=100;
var y=100;
var width=50;
var height=50;
context.translate(x+.5*width, y+.5*height);
context.scale(2,2);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
清除了画布的三种方法
- context.clearRect(x,y,width,height); // 可以指定起始点x,y位置以及宽,高来清除画布
- 简单填充 使用一个新的背景色简单填充整个画布,这样就可以清除当前内容,如:
context.fillSyle = "000000";
context.fillRect(0,0,canvas.width,canvas.height);
- 重置画布宽高,当前画布内容会移除
var w = canvas.width;
var h = canvas.height;
canvas.width = w;
canvas.height = h;
不断清除画布做下落动画
var yOffset=0;
function drawScreen(){
context.clearRect(0,0,theCanvas.width,theCanvas.height);
var currentPath=context.beginPath();
context.strokeStyle = "red"; //need list of available colors
context.lineWidth=5;
context.moveTo(0, 0+yOffset);
context.lineTo(50, 0+yOffset);
context.lineTo(50,50+yOffset);
context.stroke();
context.closePath();
yOffset+=1;
}
function gameLoop() {
window.setTimeout(gameLoop, 20);
drawScreen()
}
gameLoop();
检查一个点是否在当前路径
使用isPointInPath(),可以方便的检测一个点是否在当前路径中
context.strokeStyle = 'red'
context.lineWidth = 5
context.moveTo(0,0)
context.lineTo(50,0)
context.lineTo(50,50)
context.stroke()
let isPointInPath1 = context.isPointInPath(0,0)
let isPointInPath2 = context.isPointInPath(10,10)
console.log('isPointInPath1=' + isPointInPath1) // true
console.log('isPointInPath2=' + isPointInPath2) // false
context.closePath()
图像操作
function render () {
ctx.fillStyle = "rgba(0, 0, 0, 1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
//白色进度条
ctx.fillStyle = "#FFFFFF";
//居中canvas.width/2,canvas.height/2
ctx.fillRect(canvas.width / 2 - 150, canvas.height / 2 + 20, 300 / this.totalAssets * this.assetsLoaded, 30);
ctx.drawImage(this.oLoaderImgData.img, canvas.width / 2 - this.oLoaderImgData.img.width / 2, canvas.height / 2 - this.oLoaderImgData.img.height / 2);
this.spinnerRot += 3 * delta;
//要执行变换,执行ctx.save方法
ctx.save();
//旋转圈x,y坐标
//将画布圆点平移到圆的中心点
ctx.translate(canvas.width / 2 - 33, canvas.height / 2 - 20);
//旋转圈
ctx.rotate(this.spinnerRot);
ctx.scale(0.5, 0.5);
ctx.drawImage(this.oLoadSpinnerImgData.img, -this.oLoadSpinnerImgData.img.width / 2, -this.oLoadSpinnerImgData.img.height / 2);
ctx.restore();
}
请记住,画布是一个单一即时模式绘图界面,因此,任何变换都会应用到整个画布。例如,绘制两个对象,首先绘制一个灰色背景矩形,然后将拼图中的当前拼板复制到想到的位置上。这是两个分离的对象。但是,当它们出现在画布上时,它们都是绘制在界面上简单的像素集合。与Flash或其他平台允许多个素材或者电影剪辑占用物理空间不同,Canvas上只有一种对象:环境。
Canvas的栈
Canvas的状态可以保存在栈中,也可以从栈中读取Canvas的状态。这一点对于游戏对象应用形状变换以及画画效果是非常重要的。因为用户希望形状变换仅作用于当前的游戏对象,而不是整个画布。在游戏中使用Canvas栈的基本步骤如下:
- 将当前对象保存在栈中
- 进行变换并绘制游戏对象
- 从栈中取出已保存的画布
function drawScreen() {
// draw background and text
context.fillStyle = '#000000';
context.fillRect(0, 0, 200, 200);
context.fillStyle = '#ffffff';
context.font = '20px sans-serif';
context.textBaseline = 'top';
context.fillText ("Player Ship - rotate", 0, 180);
//transformation
var angleInRadians = rotation * Math.PI / 180;
context.save(); //save current state in stack
context.setTransform(1,0,0,1,0,0); // reset to identity
//translate the canvas origin to the center of the player
context.translate(x,y);
context.rotate(angleInRadians);
//drawShip
context.strokeStyle = '#ffffff';
context.beginPath();
context.moveTo(10,0);
context.lineTo(19,19);
context.lineTo(10,9);
context.moveTo(9,9);
context.lineTo(0,19);
context.lineTo(9,0);
context.stroke();
context.closePath();
//restore context
context.restore(); //pop old state on to screen
//add to rotation
rotation++;
}