博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一,canvas元素

1 为了防止浏览器不支持canvas元素,我们设置“后备内容”(fallback content),下面紫色的字即为后备内容

<canvas id="canvas" width="600" height="300">您的浏览器不支持canvas,可以选择升级您的浏览器</canvas>

2 开发基于canvas的应用程序的最基本的几个操作

  1),使用document.getElementById()方法获取到指向canvas的引用

  2),在步骤1)中获取到的canvas对象上调用getContext("2d")方法,获取绘图环境变量(2d为小写)

  3),使用第二步中获取到的绘图环境对象在canvas元素上进行绘制

3 关于canvas默认的一些属性

  1)canvas元素及其绘图表面的大小,默认为300×150像素,

  2)其背景色默认与其父元素的一致

  3)在设置canvas宽度和高度的时候,不能使用px后缀,根据canvas规范,canvas元素的width和height取值只能是非负整数

tip:虽然支持canvas的浏览器都允许设定canvas元素的width和height的时候使用px后缀,但是根据canvas规范书,canvas元素的width和height属性只能是非负整数

4 canvas元素的大小与绘图表面的大小

  canvas元素有两套尺寸,一套是元素本身的大小,另外一套是元素绘图表面的大小

当在html中使用width和height属性修改canvas的大小或者使用js代码设置canvas元素的width和height的时候,

其实不仅修改了canvas的大小,还修改了canvas绘图表面的大小,

但是如果通过css来设置canvas的大小,就只会改变元素本身的大小,不会影响绘图表面的尺寸,

当canvas元素的大小不符合其绘图表面的大小时,浏览器会自动对其绘图表面进行缩放,使其符合canvas元素的大小,这将导致奇怪,无用的效果

<!DOCTYPE html> 
<html> 
<head>
     
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
    <meta name="description" content="" />
    <meta name="keywords" content="" />
    <title>1</title> 
</head>

<body>
    <canvas id="canvas1"></canvas>    
    <canvas id="canvas2"></canvas>
    <div id="ca1"></div>
    <div id="ca2"></div>
    <style type="text/css">
             #canvas1 { 
                width:400px; 
                height:300px; 
                border:1px solid #000;
            }
            #canvas2 { 
                border:1px solid #000;
            }
    </style>
    <script>
        var canvas1=document.getElementById("canvas1");
        var ca1=document.getElementById("ca1");
        var context1=canvas1.getContext("2d");        
        context1.fillStyle="blue";
        context1.font="38px";
        context1.fillText("hello",100,100);
        var box1=canvas1.getBoundingClientRect();
        ca1.innerHTML="canvas默认的宽高是300×150<br/>CSS设置了canvas1的尺寸是400×300<br/>"+"canvas1的绘图表面的宽:"+canvas1.width+"  canvas1绘图表面的高:"+canvas1.height+"<br/>canvas1元素的宽:"+box1.width+"  canvas1元素的高:"+box1.height+"<br/>虽然canvas1绘图表面小于canvas1元素本身的大小,但是浏览器将绘图表面进行了缩放,所以hello字体也随之被拉伸";
        
        
        var canvas2=document.getElementById("canvas2");
        var ca2=document.getElementById("ca2");
        canvas2.width=400;
        canvas2.height=300;        
        var context2=canvas2.getContext("2d");        
        context2.fillStyle="blue";
        context2.font="38px";
        context2.fillText("hello",100,100);
        var box2=canvas2.getBoundingClientRect();
        ca2.innerHTML="canvas默认的宽高是300×150<br/>JS设置了canvas2的尺寸是400×300<br/>"+"canvas2的绘图表面的宽:"+canvas2.width+"  canvas2绘图表面的高:"+canvas2.height+"<br/>canvas2元素的宽:"+box2.width+"  canvas2元素的高:"+box2.height;
    </script>
</body>
</html>

 

关于canvas元素本身的宽高和canvas绘图环境的宽高其实真的很重要,我在一个项目中,忘记设置了canvas绘图环境的高度,导致绘制出来的图形在y轴上被拉伸,还一直找不到原因,还以为是取的坐标不对,在各种调整y轴的坐标,无意中发现只设置了宽度没有设置高度,整整浪费了我两天的时间,所以如果发现所绘制的图形被拉伸变形了(除了使用drawImage的参数将其拉伸),那很有可能就是canvas绘图环境的宽高忘记了设置

5 canvas元素的属性和方法

  canvas主要的功能是由绘图环境提供的,所以canvas元素本身的属性和方法有限,只有两个属性和三个方法

  属性:width和height,设置canvas元素及其绘图表面的大小,不需要px

  方法:1)getContext方法获取与canvas相对应的环境对象

  2)toDataURL(type,quality),返回一个数据地址(data URL),可以将其设置为img元素的src属性。

  第一个参数type指定图像的类型,例如image/jpeg,image/png,默认是image/png,

  第二个参数是0-1.0的double值,表示图像的显示质量

  3)toBlob(callback,type,quality)创建一个可以表示此canvas元素图像文件的Blob,

  第一个参数callback是回调函数,浏览器会以一个指向Blob引用为参数,来调用该函数,

  第二个参数type是图像类型,默认是image/png,

  第三个参数quality是图像显示的质量,0-1.0

二,canvas的绘图环境

1  canvas元素仅仅是为了充当绘图环境对象的容器存在,canvas的绘图环境对象才提供了全部的绘制功能,

  本书只关注2d的绘图环境,绘图环境的2d对象全称是CanvasRenderingContext2D对象

2 CanvasRenderingContext2D对象的属性

属性名称 简介
canvas

环境对象通过该属性指向canvas元素,即使用getContext方法获取到context,context再通过canvas属性获取到canvas元素

经常的用法是context.canvas.width/context.canvas.height

 fillStyle

 用于填充操作,包括填充的单一的颜色,以及渐变色还有图案等

 

 font

该属性是指在fillText和strokeText方法绘制文字的时候的字型

 

 globalAlpha

 全局透明度的设置,0-1.0,浏览器会将每个像素的alpha值都与这个值相乘后得到的值赋值给该像素的alpha

 

 globalCompsiteOperation

 设置两个物体绘制时候的叠加样式

 

 lineCap

该属性告诉浏览器如何绘制线段的端点,有butt(默认。向线条的每个末端添加平直的边缘)/round(圆)/square(方)

 

 lineWidth

线段的宽度

 

 lineJoin

该值告诉浏览器如何绘制线段相交时的交点,有bevel(创建斜角)/round(圆角)/miter(尖角)/,默认是miter

 

 miterLimit

告诉浏览器如何绘制miter形式的线段交点

 

 shadowBlur

告诉浏览器如何延伸阴影效果,但是注意这里的值不是阴影的像素长度,而是代表高斯模糊方程中的参数值

后面可以详细看一下高斯模糊方程

 shadowColor 告诉浏览器何种颜色绘制阴影,通常使用半透明色作为该属性的值
 shadowOffsetX 以像素为单位,指定阴影效果的水平偏移
 shadowOffsetY 以像素为单位,指定阴影效果垂直方向的偏移
 strokeStyle  指定对路径绘制时所使用的绘制风格,可以是单一的颜色,渐变色或者图案
 textAlign 指定使用fillText和strokeText方法绘制文字时,所绘文字的水平对齐方式
 textBaseline  指定使用fillText和strokeText方法绘制文字时,所绘文字的垂直对齐方式

3d绘图环境WebGL,在canvas中,有一个与2d绘图环境相对应的3d绘图环境,叫做WebGL,有时间看一下这个WebGL的标准:https://www.khronos.org/registry/webgl/specs/latest/

 

3 canvas状态的保存和恢复

我们在上面所罗列的canvas绘图环境的所有属性,在进行绘制操作的过程中可能需要频繁的设置这些值,有的时候可能只是临时改变一下,然后又要一个一个的设置改变回来,很是繁琐,

所以canvas的api提供了save和restore方法,用来保存当前canvas绘图环境的所有属性

注意save和restore是用来保存和恢复上面罗列的canvas绘图环境的所有的属性的,并不是保存和恢复当前画布的状态的

save方法将当前的绘图环境属性值压入栈顶,对应的restore方法将其从栈顶弹出,因此save和restore是嵌套调用的

save方法除了可以保存canvas绘图环境的所有属性之外,还可以保存当前坐标的变换剪辑区域

canvas的状态并不包括当前的路径或者位图,路径只能通过调用beginPath来重置,位图其实是canvas的一个属性,并不是绘图环境的属性,但是位图可以通过绘图环境的getImageData来获取

 5 谈一谈User Agent

在canvas规范书中,将canvas的实现者称之为User Agent(翻译为“用户代理”,是指代表软件用户发出行为指令的软件程序),

因此,任何软件都可以实现canvas元素的功能,并不是只有浏览器才可以。

 6 规范:

  1)基于网页的动画

    长久以来我们都是使用setTimeout或者setInterval方法来制作基于网页的动画,但是该方法并不适合对性能要求很高的动画,

    因此在后面我们将使用window.requestAnimationFrame方法(基于脚本的定时控制动画)来取代它们,

    该方法定义在“基于脚本的定时控制动画”的规范中,可以了解一下http://www.w3.org/TR/animation-timing

  2) 本书在后面的章节中会向读者展示如何将html5的视频和音频元素集成于基于canvas的应用程序中,因此我们可以了解一下html5的    视频和音频的规范文件:http://www.w3.org/TR/html5/video.html

  3)canvas规范,其中WHATWG维护了一份,W3C维护了一份单独的规范 ,网址:http://dev.w3.org/html5/2dcontext

 

7 性能

  这里用到的性能分析的工具主要有三个,profiler,timeLine和jsperf

其中profile和时间轴是由浏览器提供的,可以利用浏览器的插件来取得

1)console.profile():在需要开始profile的地方插入console.profile(),在结束profile的地方插入console.profileEnd()

function sum(){
    add1();
    add2();
    add3();
}
function add1(){
    for(var i=0;i<10000;i++){
    }
}
function add2(){
    for(var i=0;i<1000;i++){
    }
}
function add3(){
    for(var i=0;i<100;i++){
    }
}
console.profile("sum");
sum();
console.profileEnd("sum");

 

    1 profile的参数:当页面中有多段代码需要监测性能的时候,我们利用向profile传递参数的方式来区分不同的性能概括

    2 占用时间:只有函数自身执行的时间,不包括其内部所调用的其他函数的执行时间

    3 时间:不仅仅是该函数自身执行的时间,还包括其内部调用的其他函数执行的时间,也就是这个函数执行的总时间  

    (add1+add2+add3+sum的相对时间=sum的时间

    4 如果一个函数的时间>占用时间,那么该函数内部一定存在调用的函数

  2)时间轴,时间轴是chrome和safari使用webkit内核的浏览器才有的(还木有看明白这个工具到底怎么使用,这里有篇资料,回头看吧  http://www.qingdou.me/3719.html)

8 基本的绘制操作:

  1 canvas可以先创建不可见的路径,稍后再调用stroke方法来描绘路径的边缘

    或者调用fill方法来对路径的内部进行填充,使路径变得可见

    调用beginPath方法开始定义路径,再调用arc方法来创建一个圆形的路径,最后调用stroke方法来使刚才定义的路径可见

  2 fillText:用来在进行文本填充,与arc方法不同,fillText方法不创建路径,立即将文本渲染到canvas上

<!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>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<style type="text/css">
#canvas{
    border:1px solid #F33;
}
</style>
</head>

<body>
    <canvas id="canvas" width="400" height="400">
    </canvas>
    <script type="text/javascript">
    var canvas=document.getElementById("canvas");
    var context=canvas.getContext("2d");
    var canvasWidth=canvas.width;
    var canvasHeight=canvas.height;
    function drawClock(){
        context.clearRect(0,0,canvasWidth,canvasHeight);
        drawCenter();
        drawCircle();
        drawNumber();
        drawHands();
    }
    function drawHands(){
        var time=new Date();
        var h=time.getHours();
        var m=time.getMinutes();     //获取当前分钟数(0-59)    
        var s=time.getSeconds();     //获取当前秒数(0-59)        
        h=h>12?h-12:h;
        console.log(h+":"+m+":"+s);
        /*一个圆是360度,等于Math.PI*2,现在将一个圆等份12份,一份的角度是30度,就是Math.PI/6,现在某个时间所占的角度就是(Math.PI/6)*h
        现在再将Math.PI/6等份60份,因为一小时是60分钟,现在分钟占的角度就是*/
        var hR=100;
        var hAngle=(Math.PI/6)*h+(Math.PI/360)*m;
        var hx=canvasWidth/2+Math.sin(hAngle)*hR;
        var hy=canvasHeight/2-Math.cos(hAngle)*hR;
        context.strokeStyle="red";
        drawHand(hx,hy);
        
        var mR=100;
        var mAngle=(Math.PI/30)*m;
        var mx=canvasWidth/2+Math.sin(mAngle)*mR;
        var my=canvasHeight/2-Math.cos(mAngle)*mR;
        context.strokeStyle="green";
        drawHand(mx,my);
        
        var sR=80;
        var sAngle=(Math.PI/30)*s;
        var sx=canvasWidth/2+Math.sin(sAngle)*sR;
        var sy=canvasHeight/2-Math.cos(sAngle)*sR;
        context.strokeStyle="yellow";
        drawHand(sx,sy);
        context.strokeStyle="black";
    }
    function drawHand(x2,y2){
        context.beginPath();
        context.moveTo(canvasWidth/2,canvasHeight/2);
        context.lineTo(x2,y2);
        context.stroke();
    }
    function drawNumber(){
        var numbers=[1,2,3,4,5,6,7,8,9,10,11,12];
        numbers.forEach(function(num){
            var x=canvasWidth/2+Math.cos(Math.PI/6*(num-3))*115;
            var y=canvasHeight/2+Math.sin(Math.PI/6*(num-3))*115;
            context.fillText(num,x,y);
        });
    }
    function drawCircle(){
        context.beginPath();
        context.arc(canvasWidth/2,canvasHeight/2,100,0,Math.PI*2,true);//圆心x,圆心y,半径,起始角度,结束角度,是否逆时针
        context.stroke();
    }
    function drawCenter(){
        context.beginPath();
        context.arc(canvasWidth/2,canvasHeight/2,1,0,Math.PI*2,true);//圆心x,圆心y,半径,起始角度,结束角度,是否逆时针
        context.stroke();        
    }
    //drawClock();
    setInterval(drawClock,1000);
    </script>
</body>
</html>

 

9 事件处理

  1 )鼠标事件

    将鼠标事件的坐标转换为canvas的坐标,浏览器通过事件对象来传递鼠标的坐标,

    浏览器通过事件对象传递给监听器的鼠标坐标是窗口的坐标,并不是相对与canvas自身的坐标,

    大多数情况下,我们需要知道鼠标事件的坐标是相对于canvas的位置,而不是整个窗口的位置,

    因此,我们需要对鼠标事件的坐标进行转换(这里用到的getBoundingClientRect方法:http://ejohn.org/blog/getboundingclientrect-is-awesome/这里有一篇jquery的作者关于getBoundingClientRect的用处的文章,有时间翻一下)

    

canvas.onmousemove=function(e){
    var bbox=canvas.getBoundingClientRect();
    var loc={};
    loc.x=e.clientX-bbox.left*(canvas.width/bbox.width);
    loc.y=e.clientY-bbox.top*(canvas.height/bbox.height);   
};

 

  代码分析:

    1 使用getBoundingClientRect方法来获取canvas元素的边界框,该边界框的坐标是相对于整个窗口的,

    由于canvas有两套尺寸,绘图表面的大小和canvas元素的大小,我们真正想要拿到的是绘图表面的大小,

    因此为了避免canvas元素大小和绘图表面大小的不同,

    我们对其进行了缩放bbox.left*(canvas.width/bbox.width)

    2 当我们处理canvas上面的鼠标事件的时候,处理完毕后,我们就不再需要浏览器再对该事件做处理,

    否则可能选中其他html元素或者改变光标的位置等,

    因此我们使用preventDefault方法,阻止浏览器对该事件做出默认的反映

    3 在html5规范出现之前,通过浏览传给事件监听器的事件对象,来获取鼠标事件发生的窗口坐标,实现方法很混乱

      有x,y,有clientX,clientY,所幸,当前支持h5的浏览器最终达成一致,都支持clientX和clientY属性

<!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>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<style type="text/css">
#canvas{
    border:1px solid #F33;
}
</style>
</head>

<body>
    <canvas id="canvas">
    </canvas>
    <script type="text/javascript">
    var canvas=document.getElementById("canvas");
    var context=canvas.getContext("2d");
    canvas.width=400;
    canvas.height=400;
    function windowToCanvas(canvas,x,y){
        var box=canvas.getBoundingClientRect();
        return {
            x:x-box.left*(canvas.width/box.width),
            y:y-box.top*(canvas.height/box.height)
            //canvas.width=300,box.width=400,也就是说画布虽然实际上被浏览器放大到了400,但是画布自己觉得自己还是300,所以应该对鼠标事件的x进行缩小
        };
    }
    function drawLevelLine(context,x,y){
        context.beginPath();
        context.moveTo(x,0);
        context.lineTo(x,y);
        context.lineTo(0,y);
        context.strokeStyle="blue";
        context.stroke();
        context.closePath();
    }    
    var img=new Image();
    img.src="http://images3.c-ctrip.com/rk/201407/x780x235.jpg";
    var timerMax=0;
    img.onload=function(){
        //drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh):
        
        context.drawImage(img,0,0);
        //console.log(111);
    }
    canvas.onmousemove=function(e){
        context.clearRect(0,0,canvas.width,canvas.height);
        context.drawImage(img,0,0);
        var loc=windowToCanvas(canvas,e.clientX,e.clientY);
    //获取鼠标事件发生相对于窗口的坐标,现在支持canvas的浏览器都支持clientX和clientY属性 drawLevelLine(context,loc.x,loc.y); console.log(context,parseInt(loc.x
-50),parseInt(loc.y-50)); //console.log(loc.x+";"+loc.y); } </script> </body> </html>

   2)键盘事件

    1 当在浏览器窗口中按下某个键的时候,浏览器将会生成键盘事件,

      这些事件发生在当前拥有焦点的html元素身上,

      如果没有html元素拥有焦点的话,事件就会上移至window和document对象

    2 canvas是一个不可获取焦点的元素,由1中文名可以获知在canvas上增加键盘的监听事件是徒劳的

      应该在document或者window对象上新增键盘事件监听器才对

    3 键盘事件分为三种:

      keydown

      keypress

      keyup

    keydown和keyup是底层的事件,几乎每次按键的时候都会触发这些事件,

    除了很少部分的组合键被系统或者浏览器吞掉,一般的键盘事件都能捕捉到。

    当用户按下某个键的时候,会触发keydown,在触发keyup之前会触发keypress事件,

    如果持续按住某个按键,则会在触发了keydown之后,触发一系列的keypress,最后触发keyup事件

    4 全世界的语言有海量的字符,另外由于keyCode知道DOMLevel3规范才被标准化,现在很少有浏览器支持该规范

    所以要弄清楚到底按下的是哪个键真的是件很头大的事情

    我们这里遵循两个简单的策略:

      1 浏览器传给事件监听器的事件对象中的keyCode属性的值,

      一般情况下,如果是可打印的字读,属性值就会是ASC码,

      也包含下述boolean属性:altKey,ctrlKey/metaKey/shiftKey

    5 由于浏览器只会在打印字符的时候才会触发keypress事件,所以要获取keypress事件的值,可以利用下面来获取事件触发时所获取的字符

var key=String.fromCharCode(event.which)

 10 绘制表面的保存和恢复

  对绘图环境状态的保存save和恢复restore,可以让开发者方便的做出临时性的状态改动

  我们利用canvas绘制环境对象的getImageData和putImageData方法对绘图表面进行保存和恢复

  注意区别:一个是绘图环境状态的保存和恢复,一个是绘图表面的保存和恢复

  tip:getImageData和putImageData来保存于恢复绘图环境的绘图表面,

    另外一种常见的用法是通过其来实现图像滤镜,先获取图像数据,处理后,再将处理后的数据恢复到canvas上

我们在前面提到使用save和restore的方式来保存绘图环境的属性以及坐标等,关于绘图环境的绘图表面的保存和重绘的方式如下:

var data=context.getImageData(起始坐标x,起始坐标y,需要保存的宽,需要保存的高)
context.putImageData(getImageData获取到的数据,需要开始绘制的起始坐标x,需要绘制的起始坐标y)

立即模式绘图系统

canvas采取的是“立即模式”来绘制图形,也就是它会立刻将你所指定的内容绘制到画布上,但是它绘制完成后会立即忘记刚才绘制的内容

与之相对的是SVG的绘图模式--“保留模式”,这种模式会维护一张所绘制图形对象的列表

二者对比,由于立即模式并不需要维护绘制对象的列表,也不需要对绘图系统传递图形对象到表格里,所以相对于保留模式,立即模式是一种更加底层的绘图模式,也更灵活

11  在canvas中使用html

  存在于DIV元素之中的半透明的链接,并且浮动在canvas上面,我们将这种div称之为“玻璃窗格”(glass pane)

  html:玻璃窗格和canvas是并列位于body下面

  

<!DOCTYPE html>
<html>
   <head>
      <title>Bouncing Balls</title>
   </head>

   <body>
      <div id='glasspane'>
         <a id='startButton'>Start</a>
      </div>

      <canvas id='canvas' width='750' height='500'>
         Canvas not supported
      </canvas>

      <script src='example.js'></script>
  </body>
</html>

 

  <style> 
         body {
            background: #dddddd;
         }

         #canvas {
            margin-left: 10px;
            margin-top: 10px;
            background: #ffffff;
            border: thin solid #aaaaaa;
         }

         #glasspane {
            position: absolute;
            left: 50px;
            top: 50px;
            padding: 0px 20px 10px 10px;
            background: rgba(0, 0, 0, 0.3);
            border: thin solid rgba(0, 0, 0, 0.6);
            color: #eeeeee;
            font-family: Droid Sans, Arial, Helvetica, sans-serif;
            font-size: 12px;
            cursor: pointer;
            -webkit-box-shadow: rgba(0,0,0,0.5) 5px 5px 20px;
            -moz-box-shadow: rgba(0,0,0,0.5) 5px 5px 20px;
            box-shadow: rgba(0,0,0,0.5) 5px 5px 20px;
         }
         #glasspane a:hover {
            color: yellow;
         }

         #glasspane a {
            text-decoration: none;
            color: #cccccc;
            font-size: 3.5em;
         }
      </style>

 

 

 

var context = document.getElementById('canvas').getContext('2d'),
    startButton = document.getElementById('startButton'),
    glasspane = document.getElementById('glasspane'),
    paused = true,
    circles = [];

drawGrid(context, 'lightgray', 10, 10);

context.lineWidth = 0.5;
context.font = '32pt Ariel';

for (var i=0; i < 100; ++i) {
   circles[i] = { 
       x: 100, 
       y: 100, 
       velocityX: 3*Math.random(), //x方向的速度
       velocityY: 3*Math.random(), //y方向的速度
       radius: 50*Math.random(),//半径
       color: 'rgba(' + (Math.random()*255).toFixed(0) + ', ' +
                        (Math.random()*255).toFixed(0) + ', ' +
                        (Math.random()*255).toFixed(0) + ', 1.0)' };
}

startButton.onclick = function(e) {
   e.preventDefault();
   e.stopPropagation();
   paused = ! paused;
   startButton.innerText = paused ? 'Start' : 'Stop';
};

glasspane.onmousedown = function(e) {
   e.preventDefault();
   e.stopPropagation();
}

context.canvas.onmousedown = function(e) {
    e.preventDefault();
    e.stopPropagation();
};

setInterval(function() {
   if (!paused) {
      context.clearRect(0, 0, context.canvas.width, context.canvas.height);
      drawGrid(context, 'lightgray', 10, 10);
   
      circles.forEach(function(circle) {
         context.beginPath();
         context.arc(circle.x, circle.y, circle.radius, 0, Math.PI*2, false);
         context.fillStyle = circle.color;
         context.fill(); 
         adjustPosition(circle);
      });
   }
}, 1000 / 60);

function adjustPosition(circle) {
    //如果当前圆的x坐标加上圆的速度加上半径大于canvas的宽,碰到右边的边界了,
    //x坐标加上圆是速度减去半径小于0,碰到左边的边界了,速度的值变为负的,也就是反方向运行
    //y坐标也是一样
   if (circle.x + circle.velocityX + circle.radius > context.canvas.width ||
       circle.x + circle.velocityX - circle.radius < 0) 
      circle.velocityX = -circle.velocityX;

   if (circle.y + circle.velocityY + circle.radius > context.canvas.height ||
       circle.y + circle.velocityY - circle.radius  < 0) 
      circle.velocityY= -circle.velocityY;

   circle.x += circle.velocityX;
   circle.y += circle.velocityY;
}

//画网格
function drawGrid(context, color, stepx, stepy) {
   context.strokeStyle = color;
   context.lineWidth = 0.5;

   for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) {
      context.beginPath();
      context.moveTo(i, 0);
      context.lineTo(i, context.canvas.height);
      context.stroke();
   }

   for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) {
      context.beginPath();
      context.moveTo(0, i);
      context.lineTo(context.canvas.width, i);
      context.stroke();
   }
}

 

 

 

  1 css规范书中规定:采用绝对定位方式的元素将会绘制在采用相对定位方式的元素之上,因此要想实现html元素位于canvas上面,

  1) canvas使用相对定位,html使用绝对定位

  2) 两者都是相对或者绝对,玻璃窗格的html元素放在canvas后面

  3)两者都采用相对或者绝对,使用z-index的值来控制谁在上面

12 不可见的HTML元素

  在上面一节中,我们已经知道了如何将静态的HTML空间与canvas联系起来使用,这节我们讲的是在用户拖动鼠标时动态的改变div元素的大小

橡皮筋式选框:有一张图片绘制在canvas上,当用户在canvas上按下鼠标并拖动,绘制出一个矩形的框,松开鼠标后,将框中的部分放大展示在canvas上

<!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>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<style type="text/css">
#canvas{
    border:1px solid #F33;
    margin:0 0 20px 20px;
    cursor:crosshair;
    padding:0;
}

#rubberbandDiv{
    position :absolute;
    border:3px solid blue;
    cursor:crosshair;
    display:none;
}
</style>
</head>

<body>
    
    <div id="rubberbandDiv">
        
    </div>
    <canvas id="canvas">
    </canvas>
    <script type="text/javascript">
    var canvas=document.getElementById("canvas");
    context=canvas.getContext("2d"),
    rubberbandDiv=document.getElementById("rubberbandDiv"),
    img=new Image(),
    mousedown={},
    rubberbandRectAngle={},
    dragging=false;
    canvas.width=800;
    canvas.height=520;
    
    function rubberbandStart(x,y){
        mousedown.x=x;
        mousedown.y=y;
        rubberbandRectAngle.left=x;
        rubberbandRectAngle.top=y;
        
        
        rubberbandDiv.style.left=x+'px';
        rubberbandDiv.style.top=y+'px';
        rubberbandDiv.style.display="inline";
        dragging=true;
    }
    
    function rubberbandStretch(x,y){
        rubberbandRectAngle.left=x<mousedown.x?x:mousedown.x;
        rubberbandRectAngle.top=y<mousedown.y?y:mousedown.y;
        rubberbandRectAngle.width=Math.abs(rubberbandRectAngle.left-x);
        rubberbandRectAngle.height=Math.abs(y-rubberbandRectAngle.top);
        rubberbandDiv.style.left=rubberbandRectAngle.left+'px';
        rubberbandDiv.style.top=rubberbandRectAngle.top+'px';
        rubberbandDiv.style.width=rubberbandRectAngle.width+'px';
        rubberbandDiv.style.height=rubberbandRectAngle.height+'px';
    }
    
    function rubberbandEnd(){
        var bbox=canvas.getBoundingClientRect();
        try{
            //可以将canvas作为图像的来源,进行drawImage,这个可以不再需要原来的image
            context.drawImage(canvas,
            rubberbandRectAngle.left-bbox.left,
            rubberbandRectAngle.top-bbox.top,
            rubberbandRectAngle.width,
            rubberbandRectAngle.height,
            0,
            0,
            canvas.width,canvas.height);
        }catch(e){
        }
        rubberbandRectAngle={top:0,left:0,width:0,height:0};
        rubberbandDiv.style.width=0;
        rubberbandDiv.style.height=0;
        rubberbandDiv.style.display="none";
    }
    
    window.onmousedown=function(e){
        var x=e.clientX,
        y=e.clientY;
        e.preventDefault();
        rubberbandStart(x,y);    
    };
    window.onmousemove=function(e){
        var x=e.clientX,
        y=e.clientY;
        e.preventDefault();
        if(dragging){
            rubberbandStretch(x,y);
        }        
    };
    window.onmouseup=function(e){
        e.preventDefault();
        rubberbandEnd();
    }
    img.onload=function(){
        context.drawImage(img,0,0,canvas.width,canvas.height);
    };
    img.src="1.jpg";
    
    </script>
</body>
</html>

13  打印canvas的内容

在默认情况下,尽管canvas对象是一幅位图,但是它并不是html中的img元素,所以用户不能像对待html中的img对象一样可以拖到桌面或者右键另存为,不过canvas的api提供了toDataURL的方法,该方法返回一个引用,该引用是指向canvas元素的数据地址的,可以将img元素的src属性设置为该地址,这样就可以创建一幅canvas的图像

<!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>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<style type="text/css">
#canvas{
    border:1px solid #F33;
}
</style>
</head>

<body>
    <canvas id="canvas" width="400" height="400">
    </canvas>
    <img id="image"/>
    <script type="text/javascript">
    //console.profile("n");
    var canvas=document.getElementById("canvas");
    var context=canvas.getContext("2d");
    var canvasWidth=canvas.width;
    var canvasHeight=canvas.height;
    function drawClock(){
        context.clearRect(0,0,canvasWidth,canvasHeight);
        context.putImageData(data,0,0);
        drawHands();
    }
    drawClock1();
    function drawClock1(){
        drawCenter();
        drawCircle();
        drawNumber();
        data=context.getImageData(0,0,canvasWidth,canvasHeight);
        //drawHands();
    }
    function drawHands(){
        var time=new Date();
        var h=time.getHours();
        var m=time.getMinutes();     //获取当前分钟数(0-59)    
        var s=time.getSeconds();     //获取当前秒数(0-59)        
        h=h>12?h-12:h;
        //console.log(h+":"+m+":"+s);
        /*一个圆是360度,等于Math.PI*2,现在将一个圆等份12份,一份的角度是30度,就是Math.PI/6,现在某个时间所占的角度就是(Math.PI/6)*h
        现在再将Math.PI/6等份60份,因为一小时是60分钟,现在分钟占的角度就是*/
        var hR=100;
        var hAngle=(Math.PI/6)*h+(Math.PI/360)*m;
        var hx=canvasWidth/2+Math.sin(hAngle)*hR;
        var hy=canvasHeight/2-Math.cos(hAngle)*hR;
        context.strokeStyle="red";
        drawHand(hx,hy);
        
        var mR=100;
        var mAngle=(Math.PI/30)*m;
        var mx=canvasWidth/2+Math.sin(mAngle)*mR;
        var my=canvasHeight/2-Math.cos(mAngle)*mR;
        context.strokeStyle="green";
        drawHand(mx,my);
        
        var sR=80;
        var sAngle=(Math.PI/30)*s;
        var sx=canvasWidth/2+Math.sin(sAngle)*sR;
        var sy=canvasHeight/2-Math.cos(sAngle)*sR;
        context.strokeStyle="yellow";
        drawHand(sx,sy);
        context.strokeStyle="black";
    }
    function drawHand(x2,y2){
        context.beginPath();
        context.moveTo(canvasWidth/2,canvasHeight/2);
        context.lineTo(x2,y2);
        context.stroke();
    }
    function drawNumber(){
        var numbers=[1,2,3,4,5,6,7,8,9,10,11,12];
        numbers.forEach(function(num){
            var x=canvasWidth/2+Math.cos(Math.PI/6*(num-3))*115;
            var y=canvasHeight/2+Math.sin(Math.PI/6*(num-3))*115;
            context.fillText(num,x,y);
        });
    }
    function drawCircle(){
        context.beginPath();
        context.arc(canvasWidth/2,canvasHeight/2,100,0,Math.PI*2,true);//圆心x,圆心y,半径,起始角度,结束角度,是否逆时针
        context.stroke();
    }
    function drawCenter(){
        context.beginPath();
        context.arc(canvasWidth/2,canvasHeight/2,1,0,Math.PI*2,true);//圆心x,圆心y,半径,起始角度,结束角度,是否逆时针
        context.stroke();        
    }
    //drawClock();
    var loop=setInterval(drawClock,1000);
    setTimeout(function(){
        document.getElementById("image");
        var ddd=canvas.toDataURL();
        image.src=ddd;
    },4000)
    //console.profileEnd("n");
    </script>
</body>
</html>

 

14 离屏canvas,又称缓冲canvas或者幕后canvas

离屏的含义就是一个不可见的canvas,将背景存储在一个或者多个离屏的canvas之中,在需要的时候将这些离屏canvas中的某一部分复制到屏幕上,可以大幅提高性能

 

<!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" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <style>
        body{margin:0;padding:0;}
        .allCanvas{
            position: relative;
            margin:50px auto;
            width:600px;
            height:400px;
            border:1px solid #000;
        }
        .vcanvas{
            position: absolute;
            display: block;
            border: 1px solid;
        }
    </style>
    <title>视频拼图</title>
</head>
<body>
    <div class="allCanvas">
        <canvas id="offScreen" width="600" height="400" style="display:none"></canvas>
    </div>
    <video id="video" src="http://www.w3school.com.cn/example/html5/mov_bbb.mp4" width="600px" height="400px" controls="control" loop="loop" style="display:block;position:absolute;top:-6000px;"></video>
    <script>
         var video = document.getElementById("video");
        var offScreenCanvas = document.getElementById("offScreen");
        var offScreenCtx = offScreenCanvas.getContext('2d');
        
        var rows = 3,
            cols = 3,
            allCanvasBox = document.querySelector(".allCanvas"),
            box=allCanvasBox.getBoundingClientRect();
            vedioWidth=600,
            vedioHeight=400,
            canvases = [];
        /*
        *这里使用了构造函数和原型相结合的方式,
        *实例的属性定义在构造函数中,那些公用的方法定义在原型中
        *例如,每个人都有自己的名字,所以名字是定义在构造函数中的,而每个人showName是一样的,所以定义在原型中
        *function Person(name){
            this.name=name;
        }    
        Person.prototype.showName=function(){
            alert(this.name);
        }
        var p1=new Person("Jim");
        var p2=new Person("Jhon");
        p1.friends.push("Tom");
        alert(p1.friends);//"Tom"
        alert(p2.friends);//""
        alert(p1.showName==p2.showName);//true
        */
        /*
        *同样的,videoCanvas的坐标和宽高,以及所处的行号和列号是私有的
        *而创建和行为是一致的.
        *new vCanvas(Math.random()*600, Math.random()*400 , vedioWidth/rows , vedioHeight/cols , j , i);
        */
        var vCanvas = function(x,y,w,h,cols,rows){
            this.x = x;
            this.y = y;
            
            this.w = w;
            this.h = h;
            console.log(this.w)//200
            console.log(this.h)//200
            this.cols = cols;
            this.rows = rows;
            this.creat();
            this.behavior();
        };
        vCanvas.prototype = {
            creat:function(){
                //这里的this  指向的是每个相应的实例
                this.canvas = document.createElement("canvas");
                allCanvasBox.appendChild(this.canvas);
                this.canvas.className = "vcanvas";
                this.canvas.id = "vc_"+(this.cols+1)*(this.rows+1);
                this.canvas.style.left = this.x+"px";
                this.canvas.style.top = this.y+"px";
                this.canvas.width = this.w;
                this.canvas.height = this.h;
            },
            behavior:function(){
                this.canvas.onmousedown = function(e){
                    e = e||window.event;
                    var that = this; 
                    var mouseDown={
                        x:e.clientX,
                        y:e.clientY
                    }
                    var thatLeft={
                        x:that.style.left.replace("px","")*1,
                        y:that.style.top.replace("px","")*1
                    }
                    window.onmousemove = function(e){
                        e = e||window.event;
                        var mouseMove = {
                            x:e.clientX,
                            y:e.clientY
                        }
                        that.style.left = mouseMove.x-box.left-(mouseDown.x-box.left-thatLeft.x) + "px";
                        that.style.top = mouseMove.y-box.top-(mouseDown.y-box.top-thatLeft.y) + "px";  
                        //mouseDown=mouseMove;
                    }
                    window.onmouseup = function(){
                        this.onmousemove = null;
                    }
                }
            }
        }
        function createCanvas(){
            for(var i=0;i<cols;i++){
                for(var j=0;j<rows;j++){
                    var canvas = new vCanvas(Math.random()*600, Math.random()*400 , vedioWidth/rows , vedioHeight/cols , j , i);
                    canvases.push(canvas);
                }
            }
        }
        
         Array.prototype.forEach = function(callback){
            for(var i=0;i<this.length;i++){
                callback.call(this[i]);
            }
        }

        var lastTime = 0;
        function initAnimate(){
            lastTime = new Date();
            createCanvas();
            animate();
        }

        function animate(){
            var newTime = new Date();
            if(newTime - lastTime > 30){
                lastTime = newTime;
                offScreenCtx.drawImage(video , 0 , 0 , vedioWidth , vedioHeight);
                canvases.forEach(function(){
                    var ctx2 = this.canvas.getContext('2d');
                    ctx2.drawImage(offScreenCanvas , this.cols*this.w , this.rows*this.h , this.w , this.h,0,0,this.w,this.h);
                    //drawImage,image,图像的起始坐标,图像的宽高drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
                });
            }
            if("requestAnimationFrame" in window){
                requestAnimationFrame(animate);
            }
            else if("webkitRequestAnimationFrame" in window){
                webkitRequestAnimationFrame(animate);
            }
            else if("msRequestAnimationFrame" in window){
                msRequestAnimationFrame(animate);
            }
            else if("mozRequestAnimationFrame" in window){
                mozRequestAnimationFrame(animate);
            }
        }

        video.play();
        initAnimate();

    </script>
</body>
</html>

 15 基础数学知识简介

  15-1:求解代数方程

  15-2:三角函数

    15-2-1:角度与弧度:180度=Math.PI弧度

    15-2-2:正弦=对边/斜边;余弦=邻边/斜边;正切:对边/邻边

  15-3 向量计算

    二维向量:大小,方向,可以用来表示力和运动

     15-3-1:向量的大小:Math.sqrt(Math.pow(v.x,2)+Math.pow(v.y,2));

    15-3-2:单位向量:长度是1的向量,剩下的只是方向,所以单位向量就是用来指示方向的向量      

var length=Math.sqrt(Math.pow(v.x,2)+Math.pow(v.y,2));
var unitV=new Vector();
unitV.x=v.x/length;
unitV.y=v.y/length;

 

     15-3-3 向量的加法和减法(平行四边形规则)

 

 

 

    15-3-4:两个向量的点积:dot=v1.x*v2.x+v1.y*v2.y

      (x1,y1).(x2,y2)=x1*x2+y1*y2得到的值只是标量,只是一个数字而已,

      该数字的着力点并不是大小,而是是否大于0,主要是判断两个向量是否在一个方向上

      当某个运动的物体同静止的物体发生碰撞的时候,如果想让运动的物体被静止的物体弹开,那么就要确保这个运动的物体在碰撞之后朝着远离静止物体的方向运动,可以通过计算两个向量的点积来解决这个问题

  15-4:根据计量单位来推导等式

    动画的移动应该以时间为基准,尤其是针对多人游戏来说,肯定不希望使用高配置电脑玩家比别人移动的快

    为了实现基于时间的运动效果,本书采用“每秒移动的像素数”作为计量移动速度的单位

    为了计算动画每帧所移动的像素数,我们需要知道:1 物体的移动速度,每秒钟x像素,2 当前动画的帧速率,每帧持续y毫秒

帧速率每秒钟刷新的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次。对影片内容而言,帧速率指每秒所显示的静止帧格数。要生成平滑连贯的动画效果,帧速率一般不小于8

 

已知:每帧持续多少毫秒,每秒移动的像素数

求:每帧要移动的像素数

例:一个对象移动速度是每秒100像素,每500毫秒换1帧,则每帧移动了多少像素数