HTML5 canvas Web 绘图

二维变形

Canvas 绘图中另一个重要的概念是 绘画状态(Drawing State),绘画状态反映了渲染上下文当前的瞬时状态,开发人员可以通过对绘画状态的保存 / 恢复操作而快速的回到之前使用的各种属性和变形操作。绘画状态主要由以下三个部分构成:
    当前的变形矩阵(transformation matrix)
    当前的裁剪区域(clipping region)
    当前上下文中的属性,比如 strokeStyle, fillType, globalAlpha, font 等等。

需要指出的是,当前路径对象以及当前的位图都不包含在绘画状态之中,路径是持续性的对象,如前文所讲,只有通过 beginPath() 操作才会进行重置,而位图则是 canvas 的属性,并非属于渲染上下文的。

开发人员可以使用 save 和 restore 两种方法来保存和恢复 canvas 状态,每调用 save 方法,都会将当前状态压入堆栈中,而相应的 restore 方法则会从堆栈中弹出一个状态,并将当前画面恢复至该状态。绘画状态在 canvas 图形变形操作中应用极为广泛,也非常重要,因为调用一个 restore 方法远比手动恢复先前状态要简单许多,因而,一个较好的习惯是在做变形操作之前先保存 canvas 状态。

二维绘图的常用变形操作在 canvas 中都可到了很好的支持,包括平移(Translate),旋转(Rotate),伸缩(Scale)等等。由于所有的变形操作都基于变形矩阵,因而开发人员始终需要记住一点的就是,一旦没有使用 save/restore 操作保持住原来的绘图状态,那么后续的绘图操作,都会在当前所应用的变形状态下完成。

ContractedBlock.gif ExpandedBlockStart.gif View Code
function DrawPointCircle() {
var canvas = document.getElementById( ' canvas ' );
if (canvas.getContext) {
var ctx = canvas.getContext( ' 2d ' );
ctx.translate(
400 , 300 ); // 将 canvas 的原点从 (0,0) 平移至(400,300)
for (i = 1 ; i <= 3 ; i ++ ) { // 绘制内外 2 层
if ((i % 2 ) == 1 ) { ctx.fillStyle = ' #00f ' ; }
else { ctx.fillStyle = ' #f00 ' ; }
ctx.save();
// 保持开始绘制每一层时的状态一致
for (j = 0 ; j <= i * 6 ; j ++ ) { // 每层生成点的数量
ctx.rotate(Math.PI / ( 3 * i)); // 绕当前原点将坐标系顺时针旋转 Math.Pi/(3*i) 度
ctx.beginPath();
ctx.arc(
0 , 20 * i, 5 , 0 , Math.PI * 2 , true );
ctx.fill();
// 使用 fillType 值填充每个点
}
ctx.restore();
}
}
}

像素级绘图

像素级别的绘图操作是 canvas 绘图区别于 SVG,VML 等绘图技术的最为明显特征之一,渲染上下文提供了 createImageData, getImageData, 和 putImageData 三种方法来进行针对像素的操作,所基于的对象都是 imageData 对象。imageData 对象包含 width、height 和 data 三个属性,其中 data 包含了 width × height × 4 个像素值,之所以乘以 4,在于每个像素都有 RGB 值和透明度 alpha 值。

清单 4 中所示代码为上一节中示例图形增添了简单的颜色反转滤镜效果,通过调用 getImageData(x,y,width,height) 方法获取以(x,y)为左上坐标的矩形区域内所有像素,而后对所有像素的 RGB 值做取反操作,最后通过 putImageData(imageData, x, y)将修改后的像素值重新绘制到在 canvas 上。

ContractedBlock.gif ExpandedBlockStart.gif View Code
function revertImage(){
var canvas = document.getElementById( ' canvas ' );
if (canvas.getContext){
var context = canvas.getContext( ' 2d ' );
// 从指定的矩形区域获取 canvas 像素数组
var imgdata = context.getImageData( 100 , 100 , 100 , 100 );
var pixels = imgdata.data;

// 遍历每个像素并对 RGB 值进行取反
for ( var i = 0 , n = pixels.length; i < n; i += 4 ){
pixels[i]
= 255 - pixels[i];
pixels[i
+ 1 ] = 255 - pixels[i + 1 ];
pixels[i
+ 2 ] = 255 - pixels[i + 2 ];
}
// 在指定位置进行像素重绘
context.putImageData(imgdata, 100 , 100 );
}
}

实现动画效果

Canvas 并非为了制作动画而出现,自然没有动画制作中帧的概念。因而,使用定时器不断的重绘 canvas 画面成为了实现动画效果的通用解决方式。Javascript 中的 setInterval(code,millisec) 方法可以按照指定的时间间隔 millisec 来反复调用 code 所指向的函数或代码串,这样,通过将绘图函数作为第一个参数传给 setInterval 方法,在每次被调用的过程中移动画面中图形的位置,来最终达到一种动画的体验。需要注意的一点是,虽然 setinterval 方法的第二个参数允许开发人员对绘图函数的调用频率进行设定,但这始终都是一种最为理想的情况,由于这种绘图频率很大程度上取决于支持 canvas 的底层 JavaScript 引擎的渲染速度以及相应绘图函数的复杂性,因而实际运行的结果往往都是要慢于指定绘图频率的。

清单 5 显示了一个小弹力球动画效果,在球没有到达四周边界时,绘图方法不断的移动所绘小球的横纵坐标。并且,在每次重绘之前,都是用 clear 方法将之前的画面清除。

ContractedBlock.gif ExpandedBlockStart.gif View Code
< script type = " text/javascript " >
var x = 0 ,y = 0 ,dx = 2 ,dy = 3 ,context2D; // 小球从(0,0)开始移动,横向步长为 2,纵向步长为 3

function draw(){
context2D.clearRect(
0 , 0 , canvas.width, canvas.height); // 清除整个 canvas 画面
drawCircle(x, y); // 使用自定义的画圆方法,在当前(x,y)坐标出画一个圆

// 判断边界值,调整 dx/dy 以改变 x/y 坐标变化方向。
if (x + dx > canvas.width || x + dx < 0 ) dx = - dx;
if (y + dy > canvas.height || y + dy < 0 ) dy = - dy;
x
+= dx;
y
+= dy;
}

window.onload
= function (){
var canvas = document.getElementById( ' canvas ' );
context2D
= canvas.getContext( ' 2d ' );
setInterval(draw,
20 ); // 设置绘图周期为 20 毫秒
}
< / script>

提高可访问性

一款优秀的 Web 应用必须要做到的就是提供给用户很好的可访问性,这包括对鼠标,键盘以及快捷键等操作的响应,canvas 画面的本质仍是一个 DOM 节点,因而开发人员可以通过常规的方法来处理响应。这里,与基于 SVG 的绘图不同,由于 SVG 是一种基于 XML 的声明式的绘图方式,因而,SVG 中任何的图形都可以作为一个独立的 DOM 节点去接收并响应特定事件,而 canvas 由于其像素绘图的本质,则只可以在 canvas 元素节点去处理。

图 5 所示示例代码,当鼠标在 canvas 中移动时,鼠标当前相对于 canvas 中的横纵坐标将实时输出到上方提示信息区域;当用户在 canvas 中单击鼠标左键,将在相应位置创建一个蓝色小球,而后用户可以通过键盘上的左 / 右方向键对蓝色小球进行控制,使其进行横向的移动。

ContractedBlock.gif ExpandedBlockStart.gif View Code

< script type = " text/javascript " >
var g_x,g_y; // 鼠标当前的坐标
var g_pointx, g_pointy; // 蓝色小球当前的坐标
var canvas;

function drawCircle(x,y){ // 以鼠标当前位置为原点绘制一个蓝色小球
var ctx = canvas.getContext( ' 2d ' );
ctx.clearRect(
0 , 0 , 300 , 300 );
ctx.fillStyle
= ' #00f ' ;
ctx.beginPath();
ctx.arc(x,y,
20 , 0 ,Math.PI * 2 , true );
ctx.fill();

g_pointx
= x;
g_pointy
= y
}

function onMouseMove(evt) {
// 获取鼠标在 canvas 中的坐标位置
if (evt.layerX || evt.layerX == 0 ) { // FireFox
g_x = evt.layerX;
g_y
= evt.layerY;
}
document.getElementById(
" xinfo " ).innerHTML = g_x;
document.getElementById(
" yinfo " ).innerHTML = g_y;
}

function onKeyPress(evt) {
var dx = 3 ; // 横向平移步长
var kbinfo = document.getElementById( " kbinfo " );

if (evt.keyCode == 39 ){
kbinfo.innerHTML
= " right " ;
if (g_x < 300 - dx) drawCircle(g_pointx + dx,g_pointy);
document.getElementById(
" xinfo " ).innerHTML = g_pointx;
}
else if (evt.keyCode == 37 ){
kbinfo.innerHTML
= " left " ;
if (g_x > dx) drawCircle(g_pointx - dx,g_pointy);
document.getElementById(
" xinfo " ).innerHTML = g_pointx;
}
}

window.onload
= function (){
canvas
= document.getElementById( ' canvas ' );
// 增加 canvas 节点对鼠标单击,移动以及键盘事件的响应函数
canvas.addEventListener( ' click ' , function (evt){drawCircle(g_x, g_y);} , false );
canvas.addEventListener(
' mousemove ' , onMouseMove, false );
canvas.addEventListener(
' keypress ' , onKeyPress, false );
canvas.focus();
// 获得焦点之后,才能够对键盘事件进行捕获
}
< / script>

转载于:https://www.cnblogs.com/xiaobuild/archive/2011/07/12/2104270.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值