zoukankan      html  css  js  c++  java
  • canvas API ,通俗的canvas基础知识(四)

    今天要讲的内容是canvas的转换功能,前面的内容没用看的同学可以出门右转,先看看前面的基础知识,废话不多说,开始进入正题吧!

    何为转换功能?熟悉css3的同学都知道,css3里面有transform,translate,scale,rotate,animation等等,这就是css3的转换功能,同样,canvas也支持,但是只是支持部分,那我们来看看,都支持哪些,和css3相比,有什么区别?

    1、scale

    scale(scaleWidth,scaleHeight)  缩放当前绘图

    参数:scaleWidth 表示缩放当前绘图的宽度,取值如0.5 = 50% ,1 = 100% , 2 = 200%以此类推 ; scaleHeight 表示缩放当前绘图的高度,取值如0.5 = 50% ,1 = 100% , 2 = 200%以此类推 

    我们可以先看看css3是什么表现:

    css3 scale(sx,sy)  sx,sy分别表示向横坐标和纵坐标的缩放向量,取值如0.5 = 50% ,1 = 100% , 2 = 200%以此类推 基本上跟canvas是一样,只是说法不一样而已,那既然用法是一样的,我们就来试一下:

    ctx.strokeStyle = 'red';
    ctx.strokeRect(5,5,50,50);
    ctx.scale(2,2);
    ctx.strokeRect(5,5,50,50);
    ctx.scale(2,2);
    ctx.strokeRect(5,5,50,50);

    咦,你会看到一个很奇怪的现象,它不是从定位的原点开始缩放的,而是偏移了原点,实际上是不仅仅是图形缩放了,就连图形的边距也缩放了,且缩放的倍数与图形倍数一致,我们看一下css3的scale会有什么样的表现:

    .box{
        width:50px;
        height:50px;
        border:1px solid #000;
        margin:20px;
    }
    .box:hover{
        -webkit-transform:scale(2,2);
    }

        

    css3的表现是以图形的中心点为原点,然后向四周缩放,由此我们得到canvas 的scale与css3的scale的不同点之一;

    不同点之二就是,css3的scale,如果x,y轴的缩放倍数一样的话,是可以缩写成一个参数的,如:

    .box:hover{
        -webkit-transform:scale(2);
    }

    效果是一样一样的,但是canvas的2个参数即使缩放倍数一样,也是不能进行简写的,必须2个参数都写才能运行;

    说到这里,我想起来在canvasAPI开篇的时候留了一个悬念,就是canvas画布的宽高设置必须在canvas标签属性上设置,而不能在css里设置,这是为什么呢?下面大家来看一个例子:

    第一组,我们用css来定义canvas的宽高,标签属性上不设样式:

    canvas{ background:#fff; width:300px; height:300px; }
    ctx.strokeStyle = 'red';
    ctx.strokeRect(5,5,50,50);

    咦,这是什么鬼?

    第二组,我们用canvas标签属性来设宽高,不用css设置:

    <canvas width="300" height="300" id="canvas">
            <span>亲,您的浏览器不支持canvas,换个浏览器试试吧!</span>
    </canvas>

    明显是在标签属性上设置的宽高是正常的,为什么css设置宽高会出现这种诡异的状况,原因是canvas本身是有默认宽高的(宽300,高150),如果在css中设置宽高,会让canvas认为,现在canvas的宽度倍自动缩放了,缩放比例为css设置的宽度/300,高的也一样,那么就可以理解了,现在css设置的宽度是300,高的是300,那么就会缩放宽=300/300,高缩放=300/150,高的自然就被拉高了一倍,所以这才是必须在canvas的属性上设置宽高的原因

    我们回到scale,我们来给给一个动态图,来看看scale的变化过程:

    var timer = null;
    ctx.strokeStyle = 'red';
    timer = setInterval(function(){
        ctx.beginPath();
        ctx.scale(1.5,1.5);
        ctx.strokeRect(5,5,50,50);
        ctx.closePath();
    },500)

    可以从这个gif图中可以看出,scale的变化是在前面的一次绘图的基础上再次缩放,然后再缩放,有人说,你这个定时器是本来就是原来的基础上再缩放一次,理所应该就是这样,但是这个效果不好看,能不能我设一个参数,然后让它累加,慢慢的缩放,且只有一个图形呢?

    嗯,这里就需要解释一个方法叫clearRect(),表示在指定的范围内清除样式,这里如果需要只有一个图形,那么就必须在下一次绘制图形之前清除掉前面的一次绘图,因为中间的时间极短,就感觉是连续的,我们先介绍一下这个clearRect()方法吧:

    clearRect(x,y,w,h) 参数:w,y表示需要清除的矩形的左上角坐标,w,h表示需要清除的矩形的宽高

    从参数可以看出,它是可以清除局部区域的像素的,如果区域设为画布,则是清除整个画布了,好了,让我们一起来写一下你想要的那种效果:

    var timer = null;
            var num = 1;
            ctx.strokeStyle = 'red';
            timer = setInterval(function(){
                
                if(parseInt(num) >=5){
                    clearInterval(timer);
                    num =5;    
                }else{
                    num +=0.1;
                }
                ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight);
                ctx.save();
                ctx.beginPath();
                ctx.scale(num,num);
                ctx.strokeRect(5,5,50,50);
                ctx.closePath();
                ctx.restore();
            },500)

    看上图,现在就可以安安静静看它是怎么缩放的了,边距和图形一起缩放,比例也是一样的,这里的效果之所以没有和上面的gif图一样,在上一次缩放的基础上缩放,是因为这一对活宝:save()和restore(),这对活宝上一篇已经讲过了,如果还是不熟悉的同学出门右转,找到API的第3篇,这里的这一对主要功能是保存当前的路径,不被其他的路径污染,这对活宝和clearRect()在做运动的时候是非常有用的,这里终点提示一下!

    2、rotate

    rotate(angle)  旋转当前绘图  参数:angle表示旋转角度,这里需要填写弧度(弧度和角度的关系,在前面就已经讲过了,不熟悉的同学可以找到API的第2篇)

    同样我们看一下css3 rotate的表现:

    .box{
        width:50px;
        height:50px;
        border:1px solid #000;
        margin:20px;
    }
    .box:hover{
        -webkit-transform:rotate(30deg);
    }

    可以看到css3的旋转是以中心为原点进行旋转,切接受的参数直接就是角度,而不是弧度,那canvas的rotate的表现是什么呢?

    ctx.fillStyle = 'red';
    ctx.fillRect(0,0,150,50);
    ctx.beginPath();
    ctx.rotate(30*Math.PI/180);
    ctx.strokeRect(0,0,150,50);
    ctx.closePath();

       红色为初始图形,黑色为旋转图形,这是将图形坐标设置画布左上角的地方的

    ctx.fillStyle = 'red';
    ctx.fillRect(50,50,150,50);
    ctx.beginPath();
    ctx.rotate(30*Math.PI/180);
    ctx.strokeRect(50,50,150,50);
    ctx.closePath();

       图形坐标设置50,50处

    ctx.fillStyle = 'red';
    ctx.fillRect(100,100,150,50);
    ctx.beginPath();
    ctx.rotate(30*Math.PI/180);
    ctx.strokeRect(100,100,150,50);
    ctx.closePath();

    图形坐标设在100,100处

    从这个3组效果中,我们可以得出这样的结论:

    1、canvas的旋转原点并不是以自身的中心为原点,而是以画布的左上角为原点,3张图的比较可以看出来

    2、图形的旋转原点也不是其自身的中心,而是其左上角为原点

    这里说了2个原点,可能不好理解哈,几个例子,比如地球,它即绕太阳转,自己本身也转,那么它让太阳转就是我们说的第一点,图形绕画布旋转,准确的来说,也是图形的左上角绕画布左上角旋转,太阳的自转就是我们说的第2点,它自己本身的旋转,只不过canvas图形中的自转不是以中心为原点的旋转,其中心在左上角,这应该就明白看吧!

    3、translate

    translate(x,y)  重新映射画布上的 (0,0) 位置,这怎么理解?通俗的将,就是重新定义坐标原点,默认原点是(0,0),用此方法会将原点改成(x,y)

    参数:x 添加到水平坐标(x)上的值  y添加到垂直坐标(y)上的值

    定义不好理解,那我们就用例子来理解:

    ctx.fillRect(10,10,100,100);
    //设置新原点
    ctx.translate(110,110);
    ctx.fillRect(10,10,100,100);

    首先我们画了一个100*100的矩形,图形坐标(10,10),因为默认原点是画布左上角,所以此图形在距离左上角(10,10)的位置,理论上说,我们再画一个一模一样的矩形,坐标也一样,2图形是会覆盖的,但是我们现在重新设置原点(110,110),刚好在第一个图形的右下角,这样方便观察,然后再画一个坐标和大小一模一样的矩形,我们来看看效果:

    第二个矩形就刚好是以(110,110)为新的原点,然后距离新原点(10,10)的距离画了一个矩形,恩,这就是translate的作用

    css3也是有translate的,我们不妨也来对比一下,下面我写一个css3的translate的例子:

    .box{
        width:150px;
        height:150px;
        border:1px solid #000;
        margin:20px;
    }
    .box:hover{
        -webkit-transform: translate(100px,0);
    }

    从gif图可以看出,css3的translate是以自身中心为原点进行平移,但是不会改变原点坐标,所以,canvas的translate跟css3的translate又不一样

    4、transform

    transform(a,b,c,d,e,f)   替换当前的变换矩阵

    参数:

    a:水平缩放绘图

    b:水平倾斜绘图

    c:垂直倾斜绘图

    d:垂直缩放绘图

    e:水平移动绘图

    f:垂直移动绘图

    参数很多,但是看这参数的解释,还是很好理解,我们都知道css3的transform是一个集合,其中包含:scale,rotate,translate,skew和matrix,并且其中的matrix(矩阵)是可以转换成前面的任何效果的,换句话说,就是matrix(矩阵)可以包含前面的任何效果,包括自身,而canvas中的transform就是扮演css3的matrix的角色,只是跟css3的效果不一样而已,前面已经对比过了,具体的原理我们在这里就不说了,如果不清楚的,可以看一下css3的matrix是什么个原理,canvas的transform跟他的原理差不多!css3 matrix看这里

    scale转成transform公式可得:

    context.scale(sx, sy)

    缩放我直接用公式来解释:

    x’=sx*x

    y’=sy*y

    (其中,sx 和sy分别表示在x轴和y轴上的缩放倍数,x和y默认为1)

    matrix(sx*x,0,0,sy*y,0,0) --> context.transform(sx*x,0,0,sy*y,0,0) -->context.transform(sx,0,0,sy,0,0)

    ctx.fillRect(10,10,100,100);
    //缩放
    ctx.transform(2,0,0,2,0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);

    rotate转化成transform

    rotate(a*Math.PI/180)

    公式推导就不推了,直接拿过来了

     context.transform(cos(a),sin(a),-sin(a),cos(a),0,0)  (a为角度)

    --> context.transform(Math.cos(a*Math.PI/180),Math.sin(a*Math.PI/180),-Math.sin(a*Math.PI/180),Math.cos(a*Math.PI/180),0,0)

    ctx.fillRect(10,10,100,100);
    //旋转
    ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);

    translate转化成transform

    translate(tx,ty)

    context.transform(1,0,0,1,tx,ty)

    ctx.fillRect(10,10,100,100);
    //平移
    ctx.transform(1,0,0,1,110,110);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);

    skew转化成transform

    虽然canvas没有skew方法,但是transform依然可以做出来

    context.transform(1,tan(ay),tan(ax),1,0,0) (ax,ay表示x方向,y方向的倾斜角度)

    -->context.transform(1,Math.tan(ay*Math.PI/180),Math.tan(ax*Math.PI/180),1,0,0)

    ctx.fillRect(10,10,100,100);
    //倾斜
    ctx.transform(1,Math.tan(30*Math.PI/180),Math.tan(30*Math.PI/180),1,0,0)
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);

    那么,如果我想实现平移,旋转,倾斜加放大呢,怎么做?那就分开写呗:

    ctx.fillRect(10,10,100,100);
    //综合
    ctx.transform(1,0,0,1,110,110);//平移
    ctx.transform(Math.cos(10*Math.PI/180),Math.sin(10*Math.PI/180),-Math.sin(10*Math.PI/180),Math.cos(30*Math.PI/180),0,0);//旋转
    ctx.transform(0.5,0,0,0.5,0,0);//缩放
    ctx.transform(1,Math.tan(30*Math.PI/180),Math.tan(30*Math.PI/180),1,0,0);//倾斜
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);

    5、setTransform

    setTransform(a,b,c,d,e,f)  当前的变换矩阵重置为单位矩阵,用法与transform相同

    参数:

    a:水平缩放绘图

    b:水平倾斜绘图

    c:垂直倾斜绘图

    d:垂直缩放绘图

    e:水平移动绘图

    f:垂直移动绘图

    怎么理解这个方法呢?

    当我们用transform时,前面的变换方法会影响到后面的变换方法,我们俗称污染,比如:

    //缩放
    ctx.transform(2,0,0,2,0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);
    ctx.beginPath();
    //旋转
    ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0);
    ctx.fillStyle="green";
    ctx.fillRect(10,10,100,100);

    前面的一个图形我想让它放大2倍,后面的我不想让它放大,而是想让它旋转30度,结果:

    后面的图形也放大了2倍,这不是我们想要的结果,有人会说,我用save()和restore()不就可以了吗?

    //缩放
    ctx.save();
    ctx.transform(2,0,0,2,0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);
    ctx.restore();
    ctx.beginPath();
    //旋转
    ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0);
    ctx.fillStyle="green";
    ctx.fillRect(10,10,100,100);

    如果你用这2个方法,我就不得不给你赞一个,说明前面的你看进去了

    但是我想说的是,我们有更好的方法,就是我们现在要讲的这个--setTransform

    //缩放
    ctx.setTransform(2,0,0,2,0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);
    ctx.beginPath();
    //旋转
    ctx.setTransform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0);
    ctx.fillStyle="green";
    ctx.fillRect(10,10,100,100);

    效果跟上面的一样,官方解释是该变换只会影响 setTransform() 方法调用之后的绘图,当然,如果你把transform和setTransform一起混用,那也是会污染的:

    //缩放
    ctx.setTransform(2,0,0,2,0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);
    ctx.beginPath();
    //旋转
    ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0);
    ctx.fillStyle="green";
    ctx.fillRect(10,10,100,100);

    要是把这2方法调个个看看:

    //缩放
    ctx.transform(2,0,0,2,0,0);
    ctx.fillStyle="red";
    ctx.fillRect(10,10,100,100);
    ctx.beginPath();
    //旋转        
    ctx.setTransform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0);
    ctx.fillStyle="green"; ctx.fillRect(10,10,100,100);

    看看,效果就又不一样了,所以,在用这些变换方法的时候,必须要弄清楚他们的作用范围和顺序,才能做出我们想要的效果,也不会污染其他的效果,这点,需谨记了!

    好了,变换部分就讲完了,感谢大家的关注,如有将的不对的地方,希望能踊跃指正,不甚感谢!

  • 相关阅读:
    python PyQt5
    传入一张图,生成它的油画版!(python实现)(转 )
    Python——画一棵漂亮的樱花树(不同种樱花+玫瑰+圣诞树喔)(转)
    Python3.7实现自动刷博客访问量(只需要输入用户id)(转)
    Python3 多线程的两种实现方式
    图片生成字符
    SqlServer性能优化 通过压缩与计算列提高性能(十一)
    json与bson的区别
    浅析Redis 和MongoDB
    Solr DocValues详解
  • 原文地址:https://www.cnblogs.com/liugang-vip/p/5389915.html
Copyright © 2011-2022 走看看