HTML5实例教程:圣诞彩蛋(四)

绘制单个圆

上一节中我们编写了一个drawRound函数,它可以一次性在Canvas上绘制出指定规格的所有圆.

但是,如果我们想要对每个圆都设计单独的响应,一次就只能绘制一个圆.

function drawRoundOne(r, r2, x, y) {  
    ctx.save();
    var i = (Math.sqrt((512 * 512) / (r2 * r2)) * y + x) * 3;
    ctx.fillStyle = rgb(r[i], r[i + 1], r[i + 2]);
    ctx.beginPath();
    ctx.arc(r2 / 2 + x * r2, r2 / 2 + y * r2, r2 / 2, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
}

将drawRound的代码提炼一下,就有了现在这个函数.

可以发现多了两个参数:x,y,它们是用来确定单个圆所在坐标的,这个坐标对应的是屏幕上圆的坐标,而不是像素坐标.

下面这张图显示出的是256px规格时圆的坐标:

圆坐标

由于我们在使用isPointInCircle函数判断鼠标坐标是否在圆内的时候已经不可避免的使用了圆的实际像素坐标(circleX,circleY),所以我们干脆把drawRoundOne重写成这样:

function drawRoundOne2(r, r2, cX, cY, x, y) {  
    ctx.save();
    var i = (Math.sqrt((512 * 512) / (r2 * r2)) * y + x) * 3;
    ctx.fillStyle = rgb(r[i], r[i + 1], r[i + 2]);
    ctx.beginPath();
    ctx.arc(cX, cY, r2 / 2, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
}

其中cX,cY是新增的参数,表示circleX和circleY.

当前情况数组

为了记录下当前画布上有哪些圆是256px的,哪些圆是128px的……我们必须新建一个数组来记录它们的情况.

而这些”情况”我们则新建一种JSON对象来表示.

这个JSON对象的结构如下:

{
  'circle':{
    'x':Num,
    'y':Num,
    'r':Num
  },
  'point':{
    'x':Num,
    'y':Num
  },
  'r2':Num
}

其中circle记录有关圆的实际坐标信息,point记录圆坐标信息,r2是该圆的直径.

当前情况数组就是一个普通的数组,不断的push和delete掉这种情况对象.

为了更方便的创建情况对象,我们编写一个工厂函数:

function circleData(cX, cY, cR, Px, Py, r2) {  
    return {
        'circle': {
            'x': cX,
            'y': cY,
            'r': cR
        },
        'point': {
            'x': Px,
            'y': Py
        },
        'r2': r2
    }
}

  整个动画最开始出现的512px大圆被初始化到当前情况数组中:

var data = [circleData(512 / 2, 512 / 2, 512 / 2, 0, 0, 512)];  

主绘制函数

既然有了当前情况的记录方案,也有了绘制单个圆的方法,那么就可以开始绘制圆了.

这里我们将不可避免的用到循环(遍历当前情况数组,然后逐个绘制).

function draw(clear){  
    if(clear){
        ctx.clearRect(0,0,512,512);
    }
    for(var i=data.length;i--;){
        var c=data[i];
        if(c){
            drawRoundOne2(rData[c.r2],c.r2,c.circle.x,c.circle.y,c.point.x,c.point.y);
        }
    }
}

这个主绘制函数有一个clear参数,这个clear如果为真,就在绘制之前进行一次画布清除.

注意这里的if(c),因为我们的鼠标碰到圆之后,原来的圆就要消除(执行delete命令),但是数组中该索引还会留下来(对应的值为undefined),所以在遍历时必须忽略掉这些不可用的圆.

还有一个要注意的地方是rData,在第二节的教程中,我们创建了各种规格圆的颜色数据,但是当时是用的是直接命名的方法,但实际上我们要多次使用到r2这个量,所以干脆将原来的r512,r256…写成这样:

var rData = [];  
rData[512] = r(512, data);  
rData[256] = r(256, data);  
rData[128] = r(128, data);  
rData[64] = r(64, data);  
rData[32] = r(32, data);  
rData[16] = r(16, data);  
rData[8] = r(8, data);  
rData[4] = r(4, data);  

这样我们就可以很方便的使用rData[r2]的形式来获得这些圆颜色数据了.

现在我们执行整个脚本第一次真正意义上给用户显示内容的绘制函数draw():

draw();  

什么?就这么一点?当然了,我们把绘制的方法和当前情况数组都整合在一起,只用一个调用函数就可以绘制问题啦.

鼠标响应

还记得上一节写的鼠标移动事件吗?

我们完成了512px圆的响应,现在只要对其稍稍改造,就能应用到所有的圆上.

canvas.addEventListener('mousemove', function(e) {  
    var mouseX = e.pageX - canvas.offsetLeft,
        mouseY = e.pageY - canvas.offsetTop;
    for(var i = data.length; i--;) {
        var c = data[i];
        if(!c || c.r2 == 4) {
            continue;
        }
        if(isPointInCircle(c.circle.x, c.circle.y, c.circle.r, mouseX, mouseY)) {
            //dosomething
        }
    }
}

这里有一个for循环,它用来遍历当前情况数组,然后用isPointInCircle函数来判断是否移动到某个圆上,之后代码再采取相应的动作.

if(!c || c.r2 == 4)的作用很简单,当c为undefined的时候(该圆已经被清除)和c.r2等于4的时候(最小的圆直径为4,至此不再进行分离).  

  那么接下来该编写鼠标移动到圆上后对圆进行分离的函数了.

每一个圆都会分离出四个小圆,而这四个小圆cX,cY,r2,pX,pY的计算方法非常简单.

于是我们把想法编写成实际的代码:

var r2 = c.r2 / 2;  
var rData = r[r2];  
var cX = c.circle.x,  
    cY = c.circle.y,
    pX = c.point.x,
    pY = c.point.y;
var newC1 = circleData(cX - r2 / 2, cY - r2 / 2, r2 / 2, pX * 2, pY * 2, r2),  
        newC2 = circleData(cX + r2 / 2, cY - r2 / 2, r2 / 2, pX * 2 + 1, pY * 2, r2),
        newC3 = circleData(cX - r2 / 2, cY + r2 / 2, r2 / 2, pX * 2, pY * 2 + 1, r2),
        newC4 = circleData(cX + r2 / 2, cY + r2 / 2, r2 / 2, pX * 2 + 1, pY * 2 + 1, r2);
delete data[i];  
data.push(newC1, newC2, newC3, newC4);  
draw(true);  

是不是非常简单?

NEXT

至此,我们的HTML5网页已经可以独立的将图片分离成圆,然后再通过这些圆完成鼠标响应,最终完成整个动画效果.

但是这还没完呢.

现在,我们需要一个后台来提供给用户的浏览器上传和下载图片的功能.

另外,我们的HTML5网页上还有很多可以提高性能和代码可读性的地方,让代码变得优雅.

在下一个教程中,将完成完全的动画效果和代码优化.

HTML5实例教程:圣诞彩蛋(五)