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

距离上一节的教程已经过了将近2周,虽然圣诞节已过,但这个教程还是要继续写下去.

上一节教程最后的代码应该是这样的:

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset='UTF-8'/>
    <title>圣诞彩蛋</title>
<style>  
    body{
        text-align:center;
        background:#E6E7E8;
    }
</style>  
</head>  
<body>  
<canvas width='512' height='512'></canvas>  
<script>  
var canvas = document.querySelector('canvas');  
var ctx = canvas.getContext('2d');  
var img = new Image();  
img.onload = function() {  
    var canvas2 = document.createElement('canvas');
    canvas2.width = canvas2.height = 512;
    var ctx2 = canvas2.getContext('2d');
    ctx2.drawImage(img, 0, 0);
    var data = ctx2.getImageData(0, 0, 512, 512).data;

    function r(r2, data) {
        if(r2 != 512) {
            var rs = r2 * r2;
            var points = 512 * 512 / rs;
            var r = new Uint8ClampedArray(points * 3);
            var size = Math.sqrt(points);
            for(var i = 0; i < points; i++) {
                var red = 0,
                    green = 0,
                    blue = 0;
                var xBegin = i % size * r2,
                    yBegin = Math.floor(i / size) * r2;
                var xEnd = xBegin + r2,
                    yEnd = yBegin + r2;
                for(var x = xBegin; x < xEnd; x++) {
                    for(var y = yBegin; y < yEnd; y++) {
                        var j = (y * 512 + x) * 4;
                        red += data[j];
                        green += data[j + 1];
                        blue += data[j + 2];
                    }
                }
                r[i * 3] = red / rs;
                r[i * 3 + 1] = green / rs;
                r[i * 3 + 2] = blue / rs;
            }
        } else {
            var r = new Uint8ClampedArray(3);
            var red = 0,
                green = 0,
                blue = 0;
            for(var i = data.length; i -= 4;) {
                red += data[i];
                green += data[i + 1];
                blue += data[i + 2];
            }
            r[0] = red / 262144;
            r[1] = green / 262144;
            r[2] = blue / 262144;
        }
        return r;
    }

    function rgb(r, g, b) {
        return 'rgb(' + r + ',' + g + ',' + b + ')';
    }
    var r512 = r(512, data);
    var r256 = r(256, data);
    var r128 = r(128, data);
    var r64 = r(64, data);
    var r32 = r(32, data);
    var r16 = r(16, data);
    var r8 = r(8, data);
    var r4 = r(4, data);
    var r2 = r(2, data);
};
img.src = 'image.png';  
</script>  
</body>  
</html>  

绘制圆

直径为512px的圆在整个动画的最开始处出现.

现在我们完成绘制圆的函数,你还可以用这个函数绘制出其他规格的圆(上一节最后的转化结果就是用这个函数绘制的).

function drawRound(r, r2) {  
    ctx.save();
    var i = 0;
    var end = Math.sqrt((512 * 512) / (r2 * r2));
    for(var y = 0; y < end; y++) {
        for(var x = 0; x < end; x++) {
            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();
            i += 3;
        }
    }
    ctx.restore();
}

该函数有两个参数:r,r2.

r是所绘制的圆数据的来源,就是我们之前将图片转化为圆后得到的数组.

r2是该圆的直径,单位为像素.

接下来我们分析一下这个函数.

变量end计算每行每列可以容纳多少个这种圆,变量i用以记录当前圆的索引.

我们用两个for循环在canvas上进行绘图,注意此时使用的是ctx而不是ctx2,因为现在绘制的内容是要反馈到用户的屏幕上的.

用以下代码绘制512px的圆:

drawRound(r512, 512);  

建立鼠标移动事件

建立一个鼠标移动的事件相当简单,因为我们编写的页面针对的是现代浏览器用户,所以直接使用Level 2的标准事件模型完成.

canvas.addEventListener('mousemove',function(e){  
    var mouseX=e.pageX-canvas.offsetLeft,
            mouseY=e.pageY-canvas.offsetTop;
});

以上这段代码为canvas添加了一个mousemove事件的监听器,并且将坐标保存到mouseX和mouseY中.

注意:e.offsetX和offsetY在Firefox浏览器中存在兼容性问题,出于兼容性考虑改用了更通用的e.page-position的方式.

检测鼠标事件是否发生在圆上

在原Flash上,只有鼠标移动到圆上才会出现响应,那么如何判断鼠标指针在圆上呢?

我们可以使用point-in-circle公式,它计算出圆心距离鼠标坐标的距离,若距离小于或等于半径则在圆内或圆上.

于是我们编写一个isPointInCircle函数:

function isPointInCircle(circleX,circleY,circleRadius,mouseX,mouseY){  
    var distance=Math.pow(mouseX-circleX,2)+Math.pow(mouseY-circleY,2);
    return distance<=Math.pow(circleRadius,2);
}

并把它运用到鼠标移动事件上:

canvas.addEventListener('mousemove',function(e){  
    var mouseX=e.pageX-canvas.offsetLeft,
            mouseY=e.pageY-canvas.offsetTop;
    if(isPointInCircle((512-1)/2,(512-1)/2,512/2,mouseX,mouseY)){
        //dosomething
    }
});

变换动画

原Flash中,512px变为4256px,256px变为4128px,128px变为4*64px时均有动画.

主要动画效果为:

  1. 原圆淡出.
  2. 4个新圆淡入.

用Canvas实现512px到256px间的动画:

var lock=false;  
canvas.addEventListener('mousemove',function(e){  
    if(!lock){
        var mouseX=e.pageX-canvas.offsetLeft,
                mouseY=e.pageY-canvas.offsetTop;
        if(isPointInCircle((512-1)/2,(512-1)/2,512/2,mouseX,mouseY)){
            lock=true;
            (function(alphaOut,alphaIn){
                if(alphaOut>=0 || alphaIn<=100){
                    ctx.save();
                    ctx.clearRect(0,0,512,512);
                    ctx.globalAlpha=alphaOut/100;
                    drawRound(r512,512);
                    ctx.globalAlpha=alphaIn/100;
                    drawRound(r256,256);
                    ctx.restore();
                    var f=arguments.callee;
                    setTimeout(function(){f(alphaOut-20,alphaIn+20)},100);
                }else{
                    lock=false;
                }
            })(100,0);
        }
    }
});

在这段代码中,我们用globalAlpha属性来修改透明度,达到淡入淡出的效果.

还使用setTimeout完成了无关时间的关键帧动画.

NEXT

在下一个教程中,将完成所有规格圆的鼠标响应.

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