Learn Html5_Canvas_2D

记得之前看过一些关于HTML5的相关知识,但是一直认为它只是一个HTML的升级版本,就没有去太关注这个,只是知道它的大体用法,以及出现的新的元素标签等内容。这几天逛了一下W3C,突然发现,原来HTML5中有很多好玩的元素,正好这几天需要坚持更新下博客,所以顺便记录一下自己所练习的东西。底下,我就简单介绍一下我在学习HTML5中的Canvas元素所遇到的一些内容。

Canvas其实就是画布的意思,记得之前做一个关于Android上的小玩意的时候,也遇到过这个,对比看了一下,整体操作思想差不多。按照我的理解,其实Canvas就是一个容器,而我们在将这个容器定义完之后,就需要在里面填充东西,就如同我们在使用ps等绘图工具的时候,建立一张画布,然后使用软件提供的例如画笔、蒙版、滤镜等各种东西来设计出我们想要的东西。不同的是一个是使用工具,一个是编码来实现,但是我更想的是,软件也是程序设计出来的,归根到底图像还是代码来实现。

2D就是二维的意思。当然还有3D,但是目前没有看那方面的内容,只是知道有一个WebGL的概念。

其实,在学习的过程中,我们还会知道SVG(Scalable Vector Graphics)可缩放矢量图,其实是用XML来进行对图像标记的方式,这也让我想起在Android中我们定义一些空间的格式,也会使用xml文件,感觉有异曲同工之意。

下面是摘抄W3C上的一段关于Canvas与SVG进行比较的话:

Canvas 与 SVG 的比较

Canvas
依赖分辨率
不支持事件处理器
弱的文本渲染能力
能够以 .png 或 .jpg 格式保存结果图像 
最适合图像密集型的游戏,其中的许多对象会被频繁重绘
   
SVG
不依赖分辨率
支持事件处理器
最适合带有大型渲染区域的应用程序(比如谷歌地图)
复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
不适合游戏应用 针对于SVG的使用方法,准备在后期来简单学习一下。

接下来,就简单介绍一下Canvas2D中涉及的一些内容。

###入门:如何进行图像的绘画

根据HTML5的规则,我们首先在页面中定义如下:

<!DOCTYPE HTML>
<html>
<body>
    <canvas id="my_cs">Your explorer does not support canvas</canvas>
</body>
<html> 我们可以通过css style来定义这块画布的大小,简单原因,我就不进行设置了。

下面,我们使用javascript来进行绘画。

<script type="text/javascript">
    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = "#cccccc";//填充颜色
    ctx.fillRect(20,20,60,100);//设置矩形
</script> 我们将会看见输出如下效果:

相关流程解释:

首先我们在页面中定义Canvas画布,然后我们在js中获得这块画布,并且使用2D的形式获得上下文,我们设置一个矩形(20,20:代表设置的图形相对于画布的偏移量,理解为x和y轴;60,80:代表此矩形的长和高)。

下面我们这样设置:

<script type="text/javascript">
    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.strokeStyle = "red";//设置笔触颜色
    ctx.strokeRect(20,20,60,100);//无填充矩形
</script> 我们会看见输出如下效果:

会发现,第一个是实心矩形,第二个是空心矩形。

###创建路径(Building Paths)

<script type="text/javascript">
    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.beginPath();//起始路径
    ctx.strokeStyle = "red";//设置路径样式
    ctx.moveTo(20,20);//设置起始位置
    ctx.lineTo(500,300);//设置路径方向
    ctx.stroke();//绘制已定义的路径,一定要加上,否则无效果
</script> 我们会看见输出如下效果:

我们会发现在html5中创建路径的方式很简单,首先要设置起始路径,然后定义坐标轴的起始地点,然后给出路线行走的方向,按照我的理解,其后台程序执行的起始就是一个类似于线性的函数,也许更复杂一点。

再来一种样式:

<script type="text/javascript">	
    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.strokeStyle = "red";
    ctx.moveTo(20,20);
    ctx.lineTo(20,150);
    ctx.lineTo(70,150);
    ctx.closePath();//从当前点到起始点的闭合
    ctx.stroke();
</script> 会看到输出如下效果:

假如我们将ctx.closePath();去掉,将会看见如下效果:

通过这两个图形的对比,我们可以更好地理解closePath()函数的含义。

下面我们简单定义一个圆形:

var canvas = document.getElementById('my_cs');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(100,70,60,0,2*Math.PI);
ctx.strokeStyle = "red";
ctx.stroke();//或者用fill()方法 将会看到如下效果:

这是我在W3C上截的一张关于圆形绘图的说明:

绘制圆形的相关语法规则如下:

context.arc(x,y,r,sAngle,eAngle,counterclockwise);

说明:

参数	描述
x	圆的中心的 x 坐标

y	圆的中心的 y 坐标
 
r	圆的半径

sAngle	起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。

eAngle	结束角,以弧度计。

counterclockwise	可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。

###线条样式(Line Style)

<script type="text/javascript">	
    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.strokeStyle = "red";
    ctx.moveTo(20,20);
    ctx.lineTo(500,300);
    ctx.lineWidth = 5.0;//设置线条的粗细
    ctx.stroke();
</script>

效果如图所示,可以与上面的进行比较:

再来看另一种样式:

<script type="text/javascript">	
    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.strokeStyle = "red";
    ctx.moveTo(20,20);
    ctx.lineTo(500,300);
    ctx.lineWidth = 20.0;//设置线条的粗细
    ctx.lineCap = "round";//可以设置butt or round or square
    ctx.stroke();
</script>

效果如下所示:

可以看到lineCap()所起到的作用就是对一条路径的结束端点添加指定样式。

###设置字体样式(Text Styles)

    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.font = "Bold italic 30px Arial";
    ctx.textAlign = "center";
    ctx.fillStyle = "red";
    ctx.fillText("高木匠",100,50);//设置实心字体
    ctx.strokeStyle = "red";//设置边框颜色
    ctx.strokeText("高木匠",100,80);//设置空心字体 具体效果如图所示:

###画布状态(Canvas State)

每一个上下文包含一个绘画状态栈,绘画状态包含如下几个方面:

当前的transformation matrix

当前的clipping region

当前的如下属性值: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, 

lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, 

globalCompositeOperation, font, textAlign, textBaseline.

js方法说明:

context.restore();//弹出当前栈的顶部状态

context.save();//将一个状态压到栈中

示例如下:

    var canvas = document.getElementById('my_cs');
    var ctx = canvas.getContext('2d');
    ctx.save();
    ctx.fillStyle = "#cccccc";
    ctx.fillRect(20,20,100,50);
    
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 5;
    ctx.shadowBlur = 10;//设置模糊度
    ctx.shadowColor = "#000000";//设置阴影颜色
    
    ctx.restore();
    
    ctx.fillStyle = "red";
    ctx.fillRect(20,20,100,50); 加与不加`save()`和`restore()`,将会出现如下两个情况:

我们会发现阴影效果的生成与缺失与方法有关。

###转换(Transformations)

transtate()偏移

代码示例:

    var canvas = document.getElementById('my_cs');
    var context = canvas.getContext('2d');
    context.save();
    
    context.fillStyle = "#cccccc";
    context.fillRect(20,20,50,50);
    
    context.restore();
    context.translate(40,40);//进行转换
    context.fillStyle = "#000000";
    context.fillRect(20,20,50,50);

效果如图所示:

我们发现黑色块位置大小和灰色块一样,但是我们使用了位移,则发生了一定位置的改变。

setTransform()和transform()

    var canvas = document.getElementById('my_cs');
    var context = canvas.getContext('2d');
    context.setTransform(1,0,0,1,0,0);
    //context.transform(2,0,0,2,0,0);//暂时屏蔽
    context.fillStyle = "#cccccc";
    context.fillRect(20,20,50,50);

效果如图:

当把上面屏蔽的内容解开的时候,效果如下图:

我们会发现图变大了。根据官网上的解释原理:

首先context.setTransform(1,0,0,1,0,0)设置为单位矩阵,然后我们通过context.transform(2,0,0,2,0,0)进行变换,我们会发现在页面中位置发生变化,大小发生变化。其实这就是线性代数中通过矩阵相乘的方式得到,根据语法:transform(a, b, c, d, e, f),相乘的矩阵为:

a	c	e
b	d	f
0	0	1

roate()旋转变换

看下面这段程序:

var ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
ctx.save();
ctx.fillStyle = "red";
ctx.fillRect(80,60,50,50);
ctx.restore();
ctx.rotate(30*Math.PI/180);
ctx.fillStyle = "#cccccc;"
ctx.fillRect(150,10,50,50);

效果如图所示:

下面一段程序是在网上看到的,我修改一下,其中用到位移和旋转变换。

 var canvas = document.getElementById('my_cs');
    var context = canvas.getContext('2d');
    context.translate(75,75);
  for (var i=1;i<6;i++){
    context.save();
    context.fillStyle = "red";
      for (var j=0;j<i*6;j++){ 
      	  context.rotate(Math.PI*2/(i*6));
          context.beginPath();
          context.arc(0,i*12.5,5,0,Math.PI*2,true);
          context.fill();
     }
    context.restore();
  } 我们会发现效果图如下:

scale(x,y)缩放变换

看下面一段程序:

    var canvas = document.getElementById('my_cs');
    var context = canvas.getContext('2d');
    context.save();
    context.fillStyle = "red";
    context.fillRect(4,4,100,100);
    context.restore();
     context.scale(2,2);//缩放两倍
    context.fillStyle = "#cccccc";
    context.fillRect(5,5,40,40);

效果图如下所示:

是不是看起来像盒模型?特别说明的是,对于scale(arg)的arg参数,当arg<1,缩小变换;arg>1,放大变换;arg=1,按照原来的像素变换。

###合成(Compositing)

合成的意思就是几个图像进行融合所发生的一系列的情况。而在HTML5的绘图当中,也提供了很多图像合成的方式,看下面几个示例。

以下几个示例,都是蓝色图先绘制,红色图后绘制。

source-over(default)

看代码示例:

var ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
ctx.fillStyle = "blue";
ctx.fillRect(50,50,60,60);
ctx.fillStyle = "red";
ctx.fillRect(20,20,60,60);

效果图如下所示:

可以看到红色块的一部分覆盖在蓝色块上。

destination-over

代码示例:

var ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
ctx.fillStyle = "blue";
ctx.fillRect(50,50,60,60);
ctx.globalCompositeOperation = "destination-over";//设置覆盖方式
ctx.fillStyle = "red";
ctx.fillRect(20,20,60,60);

效果如图所示:

可以看到蓝色快覆盖在红色块上。

source-atop

ctx.globalCompositeOperation = "source-atop";//设置覆盖方式

destination-atop

ctx.globalCompositeOperation = "destination-atop";//设置覆盖方式

source-in

ctx.globalCompositeOperation = "source-in";//设置覆盖方式

destination-in

ctx.globalCompositeOperation = "destination-in";//设置覆盖方式

source-out

ctx.globalCompositeOperation = "source-out";//设置覆盖方式

destination-out

ctx.globalCompositeOperation = "destination-out";//设置覆盖方式

lighter

ctx.globalCompositeOperation = "lighter";//设置覆盖方式

我们会发现重叠的区域会进行色彩的重新合成,官方称为加色处理

darker

ctx.globalCompositeOperation = "darker";//设置覆盖方式

我们会发现重叠的区域会进行色彩的重新合成,官方称为减色处理

copy

 ctx.globalCompositeOperation = "copy";//设置覆盖方式

我们会发现只有新图形会保留下来。

xor

ctx.globalCompositeOperation = "xor";//设置覆盖方式

重叠的部分会保持透明。

globalAlpha = [=value]设置透明效果

在以上代码中加入:

ctx.globalAlpha = 0.1;

看如下效果:

我们会发现红色块变成透明效果了,当然这在css中也可以通过filter进行控制。

###设置渐变(Gradient)

我们在ps中都知道渐变这个概念,在html5中分为线性渐变、径向渐变,它们都可以使用代码来进行实现,具体如下。

线性渐变

可以先看如下代码:

var ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
var gradient = ctx.createLinearGradient(0,2,420,2);//设置线性渐变
gradient.addColorStop(0,'rgba(200,0,0,0.8)');
gradient.addColorStop(0.5,'rgba(0,200,0,0.8)');
gradient.addColorStop(0,'rgba(200,0,200,0.8)');
ctx.strokeStyle = "#99dd11";
ctx.fillStyle = gradient;
ctx.lineWidth = 10;
ctx.fillRect(20,20,200,100);
ctx.strokeRect(20,20,200,100);

addColorStop(offset,color)表示在指定位置设置渐变

效果图如下所示:

径向渐变

代码如下:

var ctx = document.getElementsByTagName('canvas')[0].getContext('2d');
var gradient = ctx.createRadialGradient(100,100,20,300,300,80);//设置线性渐变
gradient.addColorStop(0,'rgba(200,0,0,0.8)');
gradient.addColorStop(0.5,'rgba(0,200,0,0.8)');
gradient.addColorStop(0,'rgba(200,0,200,0.8)');
ctx.strokeStyle = "#99dd11";
ctx.fillStyle = gradient;
ctx.lineWidth = 10;
ctx.fillRect(20,20,200,100);
ctx.strokeRect(20,20,200,100);

看一下效果图:

看起来就是一个手电筒效果,绘制过程都有相关的解释,感兴趣的可以到官网上或者找相关资料来看,这里我就不说了。

###图像处理(Drawing images to the canvas)

我们想将女神苏菲玛索的照片加载到画布中,代码如下:

var ctx = document.getElementById("myca").getContext('2d');
var image = new Image();
image.onload = function(){
  ctx.drawImage(image,10,10);

}
image.src = 'test.jpg';

效果图如下:

下面我们为图片设定指定宽高,代码如下:

var ctx = document.getElementById("myca").getContext('2d');
var image = new Image();
image.onload = function(){
  ctx.drawImage(image,10,10,200,100);

}
image.src = 'test.jpg';

效果图如下:

图片操作中还会涉及到相关剪裁操作,代码如下:

var ctx = document.getElementById("myca").getContext('2d');
var image = new Image();
image.onload = function(){
  ctx.drawImage(image,20,20,300,200,10,10,500,340);

}
image.src = 'test.jpg';

效果图如下:

整体感觉就是将图片放大后,再自行进行相应区域的剪裁,效果还是可以。

操作语法为:drawImage(image,sx,sy,swidth,sheight,x,y,width,height)

sx:剪裁的x坐标;sy:剪裁的y坐标;swidth:剪裁的宽度;sheight:剪裁的高度

其实图像操作还涉及到若干内容,包括对像素、色彩的调控等等,以后等把色彩原理、图像理论学扎实了再来研究一下。

###像素操作(Pixel manipulation)

下面我们通过像素操作来绘制一幅图像,代码如下:

var ctx = document.getElementById("myca").getContext('2d');
var imgData=ctx.createImageData(100,100);
for (var i=0;i<imgData.data.length;i+=4)
{
 imgData.data[i+0]=128;
 imgData.data[i+1]=128;
 imgData.data[i+2]=128;
 imgData.data[i+3]=128;
}
ctx.putImageData(imgData,10,10);

效果图如下:

上面的代码示例分别通过R、G、B、Alpha来进行对图像进行操作,这里涉及一些常用的色彩原理,可以去补充一下。

我们还可以获得一个已经定义区域的像素内容,并进行相关像素操作,代码如下:

var ctx = document.getElementById("myca").getContext('2d');
var imgData=ctx.createImageData(100,100);
for (var i=0;i<imgData.data.length;i+=4)
{
  imgData.data[i+0]=128;
  imgData.data[i+1]=128;
  imgData.data[i+2]=128;
  imgData.data[i+3]=128;
}
ctx.putImageData(imgData,10,10);
var icopy = ctx.getImageData(10,10,100,100);
for (var i=0;i<icopy.data.length;i+=4)
{
  icopy.data[i+0]=256;
  icopy.data[i+1]=0;
  icopy.data[i+2]=0;
  icopy.data[i+3]=128;
}
ctx.putImageData(icopy,20,20);

效果图如下:

其实HTML5还涉及到很多内容,可以去官网上去看一下具体的怎样来做。但是在学习的过程中,我发现,html5中涉及到如何解析你写的东西,包括如何在底层将这些效果渲染出来,我觉得这才是真正学习好一门知识或者语言的思考方式。

(完)

###参考资料

1、W3C官方学习网站–html5部分

2、W3C HTML5 2D Canvas标准

3、Canvas API

4、HTML5 web开发指南



Previous     Next
mjgao /