• TML5之Canvas


    知识准备 - 坐标系

      在真正开始总结变换之前,我们需要先了解一下canvas的相关坐标系知识。

    “像素坐标系”:在HTML中,我们会设置canvas的属性:width和height,它们是以像素为单位的,它们描述了canvas最终的呈现区域,我形象称之为“像素坐标”(自创,不是很贴切,行家别见笑),这个坐标系原点在canvas的左上角,这个坐标系当canvas创建完成以后,就不会变了(当然了,修改width与height的时候会变的),原点一直位于左上角;x与y各有多少像素,都已经由width和height决定了。说白了,这个东西就像画画时的画布,你给多大就多大。

    “网格坐标系”:在绘图的时候使用的坐标系。我们绘图时所有的单位使用的并不是像素坐标,而是这个称为网格的坐标系。为了在有限的画布内,画出各种比例的图形,我们可能就要对这个坐标系进行各种变换(平移、旋转、缩放)。所以后面总结的各种变换都是针对网格坐标系的。

      这两个坐标系的关系其实就像显示器与桌面的关系一样,显示器就相当于像素坐标系,它的点都是固定的,造出来什么样就什么样。桌面就像是网格坐标系,我们可以随时移动,旋转桌面,修改桌面分辨率来看更多的内容。

      在canvas中,默认情况下,网格坐标与像素坐标是一一对应的,原点都在左上角,每1个网格单位对应1个像素单位。canvas里的所有物体的位置都是相对于网格坐标的原点而言的。如下面图中所示,默认情况下,蓝色方块的位置就是距左边x单位和距上边y单位(坐标(x, y))。

    知识准备 - 状态保持
      在正式介绍变形之前,还需要先了解一下两个绘制复杂图形就必不可少的方法,这两个方法在变形中应用的相当广泛。
      保存状态:context.save()
      恢复状态:context.restore()
      save和restore方法是用来保存和恢复canvas状态的,都没有参数。canvas的状态就是当前画面应用的所有样式和变形的一个快照。
      canvas状态是以堆(stack)的方式保存的,每一次调用save方法,当前的状态就会被推入堆中保存起来。这种状态包括:
    • 当前应用的变形(即移动,旋转和缩放);
    • 所有样式:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值;
    • 当前的裁切路径(clipping path)。
      你可以调用任意多次save方法,将canvas状态入栈。每一次调用restore方法,上一个保存的状态就从堆中弹出,所有设定都恢复。
    下面的例子能很容易的说明save与restore的使用方法:

    function draw() { 
      var ctx = document.getElementById('lesson01').getContext('2d'); 
     
      ctx.fillRect(0,0,150,150);   // Draw a rectangle with default settings 
      ctx.save();                  // Save the default state 
     
      ctx.fillStyle = '#09F'       // Make changes to the settings 
      ctx.fillRect(15,15,120,120); // Draw a rectangle with new settings 
     
      ctx.save();                  // Save the current state 
      ctx.fillStyle = '#FFF'       // Make changes to the settings 
      ctx.globalAlpha = 0.5;     
      ctx.fillRect(30,30,90,90);   // Draw a rectangle with new settings 
     
      ctx.restore();               // Restore previous state 
      ctx.fillRect(45,45,60,60);   // Draw a rectangle with restored settings 
     
      ctx.restore();               // Restore original state 
      ctx.fillRect(60,60,30,30);   // Draw a rectangle with restored settings 
    }
    

      

    在上面的例子中可以看到,如果每次都手动修改各个样式的值,那将会很麻烦,特别是样式的值很多的时候,更是容易出错。这个时候使用save/restore还是很方便的。

    变换
      学过图形学的都知道,变换有这么几种:移动,旋转和缩放。为了弄清楚变换的效果,我们一定要理解,变换的目标是哪个。上面我也说了,这些变换都是针对网格坐标系的。下面分别看一下这些变换。
    平移变换:将网格坐标系的原点移动指定的偏移量。
    context.translate(x, y)
    translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量。
    在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用restore方法比手动恢复原先的状态要简单得多。特别是在循环中,更要注意保存和恢复canvas的状态。
    旋转变换:将网格坐标系沿着自己的原点顺时针旋转指定的角度。
    context.rotate(angle)
    这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。旋转的中心点始终是 canvas 的原点。
    缩放变换:将网格坐标系的坐标单位按照指定的比例进行缩小或放大。
    context.scale(x, y)
    scale 方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。
      因为像素大小是不变的,所以这个变换实际的效果就是同样大小的画布内,能画的东西多了或少了。
      变换例子如下:
    function draw() { 
      var ctx = document.getElementById('lesson01').getContext('2d'); 
      ctx.lineWidth = 1.5; 
      ctx.fillRect(0,0,300,300);  

      ctx.translate(150,150); 
      ctx.rotate(Math.PI/4);
      ctx.scale(0.5,0.5);
      ctx.clearRect(-40,-40, 80,80);
    }

    变换矩阵:所有的变换其实都可以用矩阵来表述。
    可以用下面两种方法直接设置变换矩阵:
    context.transform(m11, m12, m21, m22, dx, dy)
    context.setTransform(m11, m12, m21, m22, dx, dy)
    第一个方法直接将当前的变形矩阵乘上下面的矩阵(注意排列的顺序):
    m11  m21  dx
    m12  m22  dy
    0       0       1
    第二个方法会重置当前的变形矩阵为单位矩阵,然后以相同的参数调用transform方法。

    html5 canvas fillRect坐标和大小的问题解决方法

    canvas的宽度和高度必须内联在canvas标签中才对

    <!doctype html> 
    <html lang="en"> 
    <head> 
    <meta charset="UTF-8"> 
    <title>Document</title> 
    </head> 
    <body> 
    <canvas id='mycanvas' width='200px' height='200px' style='background:yellow'></canvas> 
    <script> 
    var c = document.getElementById('mycanvas'); 
    var ctx = c.getContext("2d"); 
    ctx.fillStyle='#f36'; 
    ctx.fillRect(100, 100, 100, 100); 
    </script> 
    </body> 
    </html> 

    所有的变换起始都是通过变换矩阵实现的,所以上述的平移,旋转,缩放都可以用相应的矩阵代替,
    平移:context.translate(dx,dy)可以使用context.transform (1,0,0,1,dx,dy)或者context.transform(0,1,1,0.dx,dy)代替。
    旋转:context.rotate(θ)可以使用context.transform(Math.cos(θ*Math.PI/180),Math.sin(θ*Math.PI/180),-Math.sin(θ*Math.PI/180),Math.cos(θ*Math.PI/180),0,0)

    或者context.transform(-Math.sin(θ*Math.PI/180),Math.cos(θ*Math.PI/180),Math.cos(θ*Math.PI/180),Math.sin(θ*Math.PI/180), 0,0)代替。
    缩放:context.scale(sx, sy)可以使用context.transform(sx,0,0,sy,0,0)或者context.transform(0,sy,sx,0, 0,0)代替。

     默认情况下,我们总是将一个图形画在另一个之上,也就是说绘制的结果受制于绘制图形的顺序。大多数情况下,这样是不够的,设置组合属性就是解决图形重叠时采取何种效果的问题。我们使用globalCompositeOperation属性来改变默认做法。
    globalCompositeOperation = type
      type是下列的12中字符串值之一:
    • source-over (default):这是默认设置,新图形会覆盖在原有内容之上。
    • source-in:新图形会仅仅出现与原有内容重叠的部分。其它区域都变成透明的。
    • ource-out:结果是只有新图形中与原有内容不重叠的部分会被绘制出来。
    • source-atop:新图形中与原有内容重叠的部分会被绘制,并覆盖于原有内容之上。
    • lighter:两图形中重叠部分作加色处理。
    • xor:重叠的部分会变成透明。
    • destination-over:会在原有内容之下绘制新图形。
    • destination-in:原有内容中与新图形重叠的部分会被保留,其它区域都变成透明的。
    • destination-out:原有内容中与新图形不重叠的部分会被保留。
    • destination-atop:原有内容中与新内容重叠的部分会被保留,并会在原有内容之下绘制新图形。
    • darker:两图形中重叠的部分作减色处理。
    • copy:只有新图形会被保留,其它都被清除掉。
      假设我们先绘制了一个蓝色的矩形,再绘制一个红色的圆形,则应用这12种组合设置的结果如下所示:

    裁剪
      与组合相关的一个问题是裁剪。其实在绘制路径的时候,最后一步将图形绘制到canvas的函数除了stroke和fill外,还有就是clip;以clip结束路径时会将当前绘制的图形当做裁剪路径,只有在裁剪路径内的图形才会显示。裁切路径属于canvas状态的一部分,可以被保存起来。如果我们在创建新裁切路径时想保留原来的裁切路径,我们需要做的就是保存一下canvas的状态。
      例如下面的例子就是先绘制Mask层的背景和裁剪路径,然后绘制的图形就只有在裁剪路径内的才可见:

    function draw() { 
      var ctx = document.getElementById('lesson01').getContext('2d'); 
      // draw mask background
      ctx.fillRect(0,0,150,150); 
      ctx.translate(75,75); 
      // create a circular clipping path 
      ctx.beginPath(); 
      ctx.arc(0,0,60,0,Math.PI*2,true); 
      ctx.clip(); 
     
      // draw background 
      var lingrad = ctx.createLinearGradient(0,-75,0,75); 
      lingrad.addColorStop(0, '#232256'); 
      lingrad.addColorStop(1, '#143778'); 
       
      ctx.fillStyle = lingrad; 
      ctx.fillRect(-75,-75,150,150); 
     
      // draw stars 
      for (var j=1;j<50;j++){ 
        ctx.save(); 
        ctx.fillStyle = '#fff'; 
        ctx.translate(75-Math.floor(Math.random()*150), 
                      75-Math.floor(Math.random()*150)); 
        drawStar(ctx,Math.floor(Math.random()*4)+2); 
        ctx.restore(); 
      }   
    } 
    function drawStar(ctx,r){ 
      ctx.save(); 
      ctx.beginPath() 
      ctx.moveTo(r,0); 
      for (var i=0;i<9;i++){ 
        ctx.rotate(Math.PI/5); 
        if(i%2 == 0) { 
          ctx.lineTo((r/0.525731)*0.200811,0); 
        } else { 
          ctx.lineTo(r,0); 
        } 
      } 
      ctx.closePath(); 
      ctx.fill(); 
      ctx.restore(); 
    }

    动画
      由于我们是用脚本去操控canvas对象,这样要实现一些交互动画也是相当容易的。只不过,canvas不是专门为动画而设计的(不像Flash),所以操作起来会有些限制。
      最大的限制就是图像一旦绘制出来,它就是一直保持那样了。如果需要移动它,我们不得不重绘所有的东西。重绘是相当费时的,而且性能依赖于电脑的速度。
    基本动画的步骤:
    1.画一帧,需要以下一些步骤:
    (1)清空 canvas
      除非接下来要画的内容会完全充满 canvas(例如背景图),否则你需要清空所有。最简单的做法就是用clearRect方法。
    (2)保存 canvas 状态
      如果你要改变一些会改变canvas状态的设置(样式,变形之类的),又要在画每一帧之时都是原始状态的话,你需要先保存一下。
    (3)绘制动画图形(animated shapes)
      这一步才是重绘动画帧。
    (4)恢复canvas状态
      如果已经保存了canvas的状态,可以先恢复它,然后重绘下一帧。
    2.按照一定的设置去计算图形的变换,重绘新的帧,画每一帧的过程是一样的。
    通常有两种方式去设置重绘的频率:
    (1)定时重绘
      第一种方式是通过setInterval和setTimeout方法来控制在设定的时间点上执行重绘。
    setInterval(animateShape,500); 
    setTimeout(animateShape,500); 
      setTimeout和setInterval的语法相同。它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码。
    不过这两个函数还是有区别的,setInterval在执行完一次代码之后,经过了那个固定的时间间隔,它还会自动重复执行代码,而setTimeout只执行一次那段代码。
    如果你不需要任何交互操作,用setInterval方法定时执行重绘是最适合的了。
    (2)特定事件重绘
      第二个方法,我们可以利用用户输入来实现操控。如果需要做一个游戏,我们可以通过监听用户交互过程中触发的事件(如document的各种keyboard,mouse事件)来控制动画效果。
    下面的例子采用第一种方式绘制一个时钟:

    function init(){ 
      clock(); 
      setInterval(clock,1000); 
    } 
    function clock(){ 
      var now = new Date(); 
      var ctx = document.getElementById('lesson01').getContext('2d'); 
      ctx.save(); 
      ctx.clearRect(0,0,150,150); 
      ctx.translate(75,75); 
      ctx.scale(0.4,0.4); 
      ctx.rotate(-Math.PI/2); 
      ctx.strokeStyle = "black"; 
      ctx.fillStyle = "white"; 
      ctx.lineWidth = 8; 
      ctx.lineCap = "round"; 
     
      // Hour marks 
      ctx.save(); 
      for (var i=0;i<12;i++){ 
        ctx.beginPath(); 
        ctx.rotate(Math.PI/6); 
        ctx.moveTo(100,0); 
        ctx.lineTo(120,0); 
        ctx.stroke(); 
      } 
      ctx.restore(); 
     
      // Minute marks 
      ctx.save(); 
      ctx.lineWidth = 5; 
      for (i=0;i<60;i++){ 
        if (i%5!=0) { 
          ctx.beginPath(); 
          ctx.moveTo(117,0); 
          ctx.lineTo(120,0); 
          ctx.stroke(); 
        } 
        ctx.rotate(Math.PI/30); 
      } 
      ctx.restore(); 
       
      var sec = now.getSeconds(); 
      var min = now.getMinutes(); 
      var hr  = now.getHours(); 
      hr = hr>=12 ? hr-12 : hr; 
     
      ctx.fillStyle = "black"; 
     
      // write Hours 
      ctx.save(); 
      ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec ) 
      ctx.lineWidth = 14; 
      ctx.beginPath(); 
      ctx.moveTo(-20,0); 
      ctx.lineTo(80,0); 
      ctx.stroke(); 
      ctx.restore(); 
     
      // write Minutes 
      ctx.save(); 
      ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec ) 
      ctx.lineWidth = 10; 
      ctx.beginPath(); 
      ctx.moveTo(-28,0); 
      ctx.lineTo(112,0); 
      ctx.stroke(); 
      ctx.restore(); 
       
      // Write seconds 
      ctx.save(); 
      ctx.rotate(sec * Math.PI/30); 
      ctx.strokeStyle = "#D40000"; 
      ctx.fillStyle = "#D40000"; 
      ctx.lineWidth = 6; 
      ctx.beginPath(); 
      ctx.moveTo(-30,0); 
      ctx.lineTo(83,0); 
      ctx.stroke(); 
      ctx.beginPath(); 
      ctx.arc(0,0,10,0,Math.PI*2,true); 
      ctx.fill(); 
      ctx.beginPath(); 
      ctx.arc(95,0,10,0,Math.PI*2,true); 
      ctx.stroke(); 
      ctx.fillStyle = "#555"; 
      ctx.arc(0,0,3,0,Math.PI*2,true); 
      ctx.fill(); 
      ctx.restore(); 
     
      ctx.beginPath(); 
      ctx.lineWidth = 14; 
      ctx.strokeStyle = '#325FA2'; 
      ctx.arc(0,0,142,0,Math.PI*2,true); 
      ctx.stroke(); 
     
      ctx.restore(); 
    } 
  • 相关阅读:
    2050大会,让世界离年青人更近,让年青人离世界更近
    2050大会,让世界离年青人更近,让年青人离世界更近
    Mybatis-Configuration-详解
    Mybatis-Configuration-详解
    .NET内置的Ajax工作原理
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/LJJ1010/p/4945909.html
Copyright © 2020-2023  润新知