Html5---鸟巢

来源:互联网 发布:南通网络推广 编辑:程序博客网 时间:2024/05/01 08:43

       由于昨天晚上陪老婆看房子去了,就没有写,今天一定补上。

       这次我们来利用CanvasRenderingContext2D的一些API一个鸟巢。涉及的知识点和技艺包括:三次贝赛尔曲线的绘制,CanvasRenderingContext2D的translate和rotate 等api.

 

第一步:椭圆绘制:

    在CanvasRenderingContext2D开放的一些曲线绘制API当中,有绘制线段、矩形和圆形的,没有绘制椭圆的,所以需要自己去封装一个绘制椭圆的方法,如:

 

  function drawEllipse(x, y, w, h) {            //您的代码        }


 

  其中,x,y为椭圆的左上角的坐标(不是中心坐标),w和y分别为长轴和短轴。在实现这个方法之前,很容易就可以想到以下面几种预选方案:

A,根据椭圆笛卡儿坐标第方程绘制;

B,根据椭圆极坐标方程绘制;

C,利用四条贝赛尔曲线绘制。

第一种和第二种方式都是基于点的,以不断地连接椭圆上的所有点来拟合一个完整的椭圆,这样会带来大量的计算和重复调用CanvasRenderingContext2D的API。在Canvas性能优化中很重要的一点就是尽量少地调用CanvasRenderingContext2D的API.比如下面两份代码的对比,第二段代码的性能远远优于第一段。

代码一:

 for (var i = 0; i < points.length-1; i++) {                var p1 = points[i];                var p2 = points[i + 1];                context.beginPath();                context.moveTo(p1.x, p1.y);                context.lineTo(p2.x, p2.y);                context.stoke();            }

代码二:

  context.beginPath();            for (var i = 0; i < points.length - 1; i++) {                var p1 = points[i];                var p2 = points[i + 1];                context.moveTo(p1.x, p1.y);                context.lineTo(p2.x, p2.y);            }            context.stoke();

 
第 三种,也是性能最好的一种,绘制过程只需要调用4次CanvasRenderingContext2D.bezierCurveTo,这样可以避免复杂的计算和大量重复调用CanvasRenderingContext2D的API。

  所以采用第三种方式来绘制椭圆。代码如下所示:

 function drawEllipse(x, y, w, h) {            var k = 0.55228475;            var ox = (w / 2) * k;            var oy = (h / 2) * k;            var xe = x + w;            var ye = y + h;            var xm = x + w / 2;            var ym = y + h / 2;            cxt.beginPath();            cxt.moveTo(x, ym);            cxt.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);            cxt.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);            cxt.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);            cxt.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);            ctx.stroke();        }


第二步:三次贝赛尔曲线

   上面通过绘制4段三次贝赛尔曲线来绘制一个椭圆。要知道以上代码中系数K的由来,可推导如下:

   曲线的参数形式为:

  B(t)=P_0 〖(1-t)〗^3+3P_1 t〖(1-t)〗^2+3P_2 t^2 (1-t)+P_3 t^2 , t∈[0,1]

 

第三步:旋转椭圆

   绘制完椭圆,需要旋转椭圆来形成鸟巢。这里的旋转不是绕上面的DrawFllipse的前两个参数x,y旋转,而是绕椭圆的中心旋转。所以仅仅使用CanvasRenderingContext2D.rotate是不够的,因为CanvasRenderingContext2D.rotate是绕画布的左上角(0,0)旋转的。所以先要把(0,0)通过CanvasRenderingContext2D.translate到椭圆的中心,然后再DrawEllipse(-a/2,-b/2,a,b)。

以上就是绕中心旋转的核心。这里还可以推广到任意图形或者图片(假设有约定的中心)。

完整的代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head>    <title></title></head><body>    <canvas id="myCanvas" width="350" height="350" style="border: solid 15px #222; background-color: #111;        color: #CCC;">你的浏览器不支持canvas</canvas>    <script src="../Scripts/jscex.jscexRequire.min.js" type="text/javascript"></script>    <script type="text/javascript">        var canvas;        var ctx;        var px = 0;        var py = 0;        function init() {            canvas = document.getElementById("myCanvas");            ctx = canvas.getContext("2d");            ctx.strokeStyle = "#fff";            ctx.translate(70, 70);        }        init();        var i = 0;        function drawEllipse(x, y, w, h) {            var k = 0.5522848;            var ox = (w / 2) * k;            var oy = (h / 2) * k;            var xe = x + w;            var ye = y + h;            var xm = x + w / 2;            var ym = y + h / 2;            ctx.beginPath();            ctx.moveTo(x, ym);            ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);            ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);            ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);            ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);            ctx.stroke();            ctx.translate(x + 70, y + 100);            px = -70;            py = -100;            ctx.rotate(10 * Math.PI * 2 / 360);        }        var ct;        var drawAsync = eval(Jscex.compile("async", function (ct) {            while (true) {                drawEllipse(px, py, 140, 200)                $await(Jscex.Async.sleep(200, ct));            }        }))        function start() {            ct = new Jscex.Async.CancellationToken();            drawAsync(ct).start();        }        function stop() {            ct.cancel();        }    </script>    <input id="Button2" type="button" value="run" onclick="return start()" />    <input id="Button1" type="button" value="stop" onclick="return stop()" /></body></html>


运行效果如下:

小结:

   通过本例子,我们了解了贝塞尔曲线的运用及其拟合椭圆的原理,然后分析了Jscex的各种取消方法和其自身的取消模型。通过这个取消模型,也可以体会到Jscex的优势,Jscex使程序员可以使用线性的思维写程序,而不必关心回调,在一些纪线性混合深度嵌套的场景下,优势特别明显。