HTML5 Canvas的图形变换

很多时候,我们绘制出一个图形之后,并不能达到我们预期的效果,这个时候,适当地运用图形的变换(transformations,如旋转和缩放等),可以创建出大量复杂多变的图形。

1、保存和恢复Canvas状态

Canvas指的是当前画面的所有样式、变形和裁切的一个快照,以堆的方式保存。save和restore方法用于保存和恢复Canvas状态,这两个方法都不需要任何参数,用法如下:

context.save();

context.restore();

sava方法可以暂时将当前状态保存在堆中,这些状态可以是各种属性(如strokeStyle、fillStyle和globalCompositeOperation等)的值。当前应用的变形、当前裁切的路径等。restore方法用于将上一个保存状态从堆中再次取出,恢复该状态的所有设置。

举一个简单的示例: 

<body>
    <canvas id="myCanvas" style="border: 1px solid #000" width="300" height="200"></canvas>
    <script>
        var c = document.getElementById("myCanvas");
        var context = c.getContext("2d");
        // 开始绘制矩形
        context.fillStyle = "red";
        context.strokeStyle = "blue";
        context.fillRect(20,20,100,100);
        context.strokeRect(10,10,120,120);
        context.fill();
        context.stroke();
        // 保存当前Canvas状态
        context.save();
        // 绘制另一个矩形
        context.fillStyle = "orange";
        context.strokeStyle = "green";
        context.fillRect(150,20,100,100);
        context.strokeRect(140,10,120,120);
        context.fill();
        context.stroke();
        // 恢复第一个矩形的状态
        context.restore();
        // 绘制两个矩形
        context.fillRect(20,140,50,50);
        context.strokeRect(140,140,50,50);
    </script>
</body>

先来看看最后的效果图:

我们保存的Canvas状态属性值是fillStyle填充为红色,strokeStyle轮廓为蓝色,当我们没有调用restore方法恢复时,依旧使用的是我们自己定义的属性值。但是,我们可以看到当我们调用restore后,绘制出来的矩形填充以及轮廓就是我们之前保存的值。

2、移动坐标空间

在上一篇Canvas元素中我们提到过,画布的坐标空间默认的是以画布左上角(0,0)为原点,x轴水平向右为正向,y轴垂直向下为正向。而在绘制图形时,我们可以使用translate方法移动坐标空间,使画布的变换矩阵发生水平和垂直方向的偏移,其用法如下:

context.translate(dx,dy);

其中dx和dy分别为坐标原点沿水平和垂直两个方向的偏移量。如下图所示:

 

当然了,在进行图形变换之前,最好先要养成使用save方法保存当前状态的好习惯。在许多情况下,使用restore方法来自动恢复原来的状态要比手动恢复更高效,特别是当重复某种运算时。接下来,我们就通过绘制一个伞状图形的示例加深对它的理解。

<body>
    <canvas id="myCanvas" style="border: 1px solid red;" width="700" height="200"></canvas>
</body>
<script>
    function drawTop(ctx,fillStyle){
        ctx.fillStyle = fillStyle;
        ctx.beginPath();
        // 以坐标原点(0,0)为圆心,半径为30px,0为开始的角度,Math.PI为结束的角度,即180度,最后一个参数表示顺时针方向绘制
        ctx.arc(0,0,30,0,Math.PI,true);
        ctx.closePath();
        ctx.fill();
    }

    function drawGrip(ctx){
        // 保存了之前定义的属性值
        ctx.save();
        ctx.fillStyle = "blue";
        // 以(-1.5,0)为起始坐标,绘制宽为1.5px,高为40px的矩形
        // 这里解释一下为什么以(-1.5,0)为坐标,是因为后面需要绘制的宽为1.5px,所以提前移动过去,绘制的时候右移就到了中心的位置
        ctx.fillRect(-1.5,0,1.5,40);
        ctx.beginPath();
        ctx.strokeStyle = "blue";
        // 这就是我们看到的伞的下部分伞钩
        ctx.arc(-5,40,4,Math.PI,Math.PI*2,true);
        ctx.stroke();
        ctx.closePath();
        // 恢复了之前定义的属性值
        ctx.restore();
    }

    function draw(){
        var ctx = document.getElementById("myCanvas").getContext("2d");
        // 垂直方向和水平方向发生了偏移
        ctx.translate(80,80);
        // 循环10次,表明有10个伞状图形
        for(var i=0;i<10;i++){
            ctx.save();
            // 水平方向发生偏移
            ctx.translate(60*i,0);
            // 绘制伞状图形的上半部分,也就是半圆部分,第二个参数表示填充颜色
            drawTop(ctx,"rgb("+(30*i)+","+(255-30*i)+",255)");
            // 绘制伞状图形的下半部分
            drawGrip(ctx);
            ctx.restore();
        }
    }

    // 在页面或图像加载完后触发
    window.onload = function(){
        draw();
    }
</script>

最后的效果图:

在这里,我只想说的一点是,关于

ctx.fillRect(-1.5,0,1.5,40);和ctx.arc(-5,40,4,Math.PI,Math.PI*2,true);

这两个语句的坐标问题,主要是取决于你后面绘制的图形的宽度。

Canvas中图形移动的实现,实际上是通过改变画布的坐标原点实现的,所谓的“移动图形”,只是看上去被移动的样子,移动的是其实是坐标空间。

3、旋转坐标空间 

rotate方法用于以原点为中心旋转Canvas,实质上旋转的是Canvas上下文对象的坐标空间,具体用法是:

context.rotate(angle);

rotate方法只有一个参数,即旋转角度angle,其顺时针方向为正方向,以弧度为单位,旋转中心是Canvas的原点。

拿上一个伞状例子来说,我现在的状态是一行排列的,那么我现在想要将它旋转成一个圆形队列,又该怎么实现呢?

    function draw(){
        var ctx = document.getElementById("myCanvas").getContext("2d");
        // 垂直方向和水平方向发生了偏移
        ctx.translate(150,150);
       

        for(var i=1;i<9;i++){
            ctx.save();
            // 图片旋转角度
            ctx.rotate(Math.PI*(2/4+i/4));
            // 水平方向发生偏移
            ctx.translate(0,-100);
            // 绘制伞状图形的上半部分,也就是半圆部分,第二个参数表示填充颜色
            drawTop(ctx,"rgb("+(30*i)+","+(255-30*i)+",255)");
            // 绘制伞状图形的下半部分
            drawGrip(ctx);
            ctx.restore();
        }
    }

其实,到最后,我们会发现,只是draw方法发生了变化。具体是什么变化呢?无非就是多了ctx.rotate(Math.PI*(2/4+i/4));和ctx.translate(0,-100);语句。那么接下来,我们一起思考为什么要使用这两个方法里的数据。

首先,我们首先明确Math.PI是180度,那么很显然旋转一圈是360度,也就是说,后面的参数(2/4+i/4)中,i=1时表示初始旋转的位置,后面的则依次递加。

其次,为什么每次都要将坐标空间沿y轴负方向移动100px呢?那是因为,如果我们不移动,最后所有的图形都会重合成一个圆,就像下面这样:

而我们实际想要的效果是什么呢?

4、缩放图形

scale方法用于增减Canvas上下文对象中的像素数目,从而实现图形或位图的放大或缩小,其用法是:

 context.scale(x,y);

其中x,y为必须接受的参数,x为横轴的缩放因子,y轴为纵轴的缩放因子,它们的值必须是正值。

若放大图形,参数值需大于1;

若缩小图形,参数值需小于1;

若参数值等于1,无任何效果。

我们可以用一个螺旋状由大到小的例子来深入理解缩放的使用:

<body>
    <canvas id="myCanvas" style="border: 1px solid red;" width="700" height="300"></canvas>
</body>
<script>
    function draw(){
        var ctx = document.getElementById("myCanvas").getContext("2d");
        // 垂直方向和水平方向发生了偏移
        ctx.translate(200,20);

        for(var i=1;i<80;i++){
            ctx.save();
            ctx.translate(30,30);
            ctx.scale(0.95,0.95);
            ctx.rotate(Math.PI/12);
            ctx.beginPath();
            ctx.fillStyle = "red";
            ctx.globalAlpha = "0.4";
            ctx.arc(0,0,50,0,Math.PI*2,true);
            ctx.closePath();
            ctx.fill();
        }
    }

    // 在页面或图像加载完后触发
    window.onload = function(){
        draw();
    }
</script>

其实,这也不难理解,我们反反复复使用的都是提到过的方法,唯一的不同是,我们现在把所有的方法整合在了一起,我们传递的参数不再是一个确定的值,所以,我们实现的效果也是千变万化的。

关于Canvas元素其实还有其他的很多方法,后续将会继续补充,希望大家能够共同进步!

猜你喜欢

转载自blog.csdn.net/weixin_38629529/article/details/83213099