《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);
}

清除了画布的三种方法

  1. context.clearRect(x,y,width,height); // 可以指定起始点x,y位置以及宽,高来清除画布
  2. 简单填充 使用一个新的背景色简单填充整个画布,这样就可以清除当前内容,如:
context.fillSyle = "000000";
context.fillRect(0,0,canvas.width,canvas.height);
  1. 重置画布宽高,当前画布内容会移除
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栈的基本步骤如下:

  1. 将当前对象保存在栈中
  2. 进行变换并绘制游戏对象
  3. 从栈中取出已保存的画布
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++;
}