HTML5系列-携手Canvas

html5里面canvas绝对是大哥级别的,canvas的绘图是基于js的处理,我们既然知道canvas,免不了就听说过svg,svg是基于xml扩展而来的,现在图表插件中国内百度的echarts是基于canvas的,国外一款非常强大的highcharts是基于svg的,svg是可伸缩标记绘图不支持低级ie,我们在下一篇html介绍博客也会简单介绍svg的处理。

 一、页面显示出我们的canvas

我们在html下面写入canvas标签,和加入背景色:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  
#myCanvas{background:#ccc;}
</style>
<title>demo</title>
</head>
<body> 
  <canvas id="myCanvas"></canvas>
</body>
<script type="text/javascript"> 
  
</script>
</html>

我们看见了灰色背景的canvas,看来我不做宽高处理,canvas是有默认的值得。

我们通过css去设置宽高:

#myCanvas{background:#ccc;height: 500px;width: 500px;}

同样canvas元素也支持宽度和高度属性的设置;我们删除css设置,加入属性设置

  <canvas id="myCanvas" height="500" width="500"></canvas>

二、canvas的API预览

颜色、样式和阴影

属性  描述

fillStyle 设置或返回用于填充绘画的颜色、渐变或模式

strokeStyle 设置或返回用于笔触的颜色、渐变或模式

shadowColor 设置或返回用于阴影的颜色

shadowBlur  设置或返回用于阴影的模糊级别

shadowOffsetX 设置或返回阴影距形状的水平距离

shadowOffsetY 设置或返回阴影距形状的垂直距离

方法  描述

createLinearGradient()  创建线性渐变(用在画布内容上)

createPattern() 在指定的方向上重复指定的元素

createRadialGradient()  创建放射状/环形的渐变(用在画布内容上)

addColorStop()  规定渐变对象中的颜色和停止位置

线条样式

属性  描述

lineCap 设置或返回线条的结束端点样式

lineJoin  设置或返回两条线相交时,所创建的拐角类型

lineWidth 设置或返回当前的线条宽度

miterLimit  设置或返回最大斜接长度

矩形

方法  描述

rect()  创建矩形

fillRect()  绘制“被填充”的矩形

strokeRect()  绘制矩形(无填充)

clearRect() 在给定的矩形内清除指定的像素

路径

方法  描述

fill()  填充当前绘图(路径)

stroke()  绘制已定义的路径

beginPath() 起始一条路径,或重置当前路径

moveTo()  把路径移动到画布中的指定点,不创建线条

closePath() 创建从当前点回到起始点的路径

lineTo()  添加一个新点,然后在画布中创建从该点到最后指定点的线条

clip()  从原始画布剪切任意形状和尺寸的区域

quadraticCurveTo()  创建二次贝塞尔曲线

bezierCurveTo() 创建三次方贝塞尔曲线

arc() 创建弧/曲线(用于创建圆形或部分圆)

arcTo() 创建两切线之间的弧/曲线

isPointInPath() 如果指定的点位于当前路径中,则返回 true,否则返回 false

转换

方法  描述

scale() 缩放当前绘图至更大或更小

rotate()  旋转当前绘图

translate() 重新映射画布上的 (0,0) 位置

transform() 替换绘图的当前转换矩阵

setTransform()  将当前转换重置为单位矩阵。然后运行 transform()

文本

属性  描述

font  设置或返回文本内容的当前字体属性

textAlign 设置或返回文本内容的当前对齐方式

textBaseline  设置或返回在绘制文本时使用的当前文本基线

方法  描述

fillText()  在画布上绘制“被填充的”文本

strokeText()  在画布上绘制文本(无填充)

measureText() 返回包含指定文本宽度的对象

文本

属性  描述

font  设置或返回文本内容的当前字体属性

textAlign 设置或返回文本内容的当前对齐方式

textBaseline  设置或返回在绘制文本时使用的当前文本基线

方法  描述

fillText()  在画布上绘制“被填充的”文本

strokeText()  在画布上绘制文本(无填充)

measureText() 返回包含指定文本宽度的对象

像素操作

属性  描述

width 返回 ImageData 对象的宽度

height  返回 ImageData 对象的高度

data  返回一个对象,其包含指定的 ImageData 对象的图像数据

方法  描述

createImageData() 创建新的、空白的 ImageData 对象

getImageData()  返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据

putImageData()  把图像数据(从指定的 ImageData 对象)放回画布上

合成

属性  描述

globalAlpha 设置或返回绘图的当前 alpha 或透明值

globalCompositeOperation  设置或返回新图像如何绘制到已有的图像上

其他

方法  描述

save()  保存当前环境的状态

restore() 返回之前保存过的路径状态和属性

createEvent()  

getContext()   

toDataURL()  

三、canvas的绘制API使用

‍‍上面我们罗列了多有dom针对canvas的api接口,canvas的操作都是通过js控制的,我们把所有接口做简单分类,在页面上预览接口的作用:‍‍

1.创建画布

canvas.getContext("2d")   ,获取canvas,调用此方法,传入2d参数可以得到2d画布

代码如下:

<script type="text/javascript"> 
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  
</script>

操作canvas,这一步几乎是准备工作,我们现在返回了画布,我们下面做一些绘制操作

2.画布直线路径绘制处理

执行绘制操作的接口:

stroke()  绘制已定义的路径

路径绘制的样式设置:

strokeStyle ="#000"设置或返回用于笔触的颜色、渐变或模式

lineWidth =10设置或返回当前的线条宽度

路径绘制的绘制点设置:

moveTo(x,y)  把路径移动到画布中的指定点,不创建线条

lineTo(x,)  添加一个新点,然后在画布中创建从该点到最后指定点的线条

路径绘制的开始和结束:

beginPath() 起始一条路径,或重置当前路径

closePath() 创建从当前点回到起始点的路径

我们在画布进行一条直线的绘制,我们设置好样式和线宽,

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =5;
  context.strokeStyle ="#000";
  context.beginPath();
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
  context.closePath();
  context.stroke();

我们的操作是:

1.创建2d画布

2.设置路径的样式(宽度和颜色)

3.开始路径

4.绘制路径开始坐标和下一个连接坐标

5.关闭路径处理,并且绘制

我们做一次做个线条绘制的实例:

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =5;
  context.strokeStyle ="#000";
  context.beginPath();
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
   context.lineTo(150,200) ;
  context.moveTo(101,10) ; 
  context.lineTo(101,50) ;
  context.closePath();
  context.stroke();

js的控制是同步处理,moveto和紧接着的lineto会分到一起,再有moveto的操作会和他下面的lineto配合处理。

如果我们去掉开始和结束路径方法的而设置,看看效果:

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =5;
  context.strokeStyle ="#000";
  
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
   context.lineTo(150,200) ;
  context.moveTo(101,10) ; 
  context.lineTo(101,50) ;
 
  context.stroke();

发现和上面没有区别,会不会因为分成2组导致开始的move和最后line不能识别了,我们留下一组路径处理:

 var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =5;
  context.strokeStyle ="#000";
  context.beginPath();
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
   context.lineTo(150,200) ;
  context.closePath();
  context.stroke();

在开始路径和结束路径处理的路径绘制move和最后的line头尾相连了,看来开始和结束路径的方法有效必须是一组路径处理。

我们绘制的路径可能多条,我们看见折线的连接点是直角,我们可能希望是圆形的,我们可进行设置

lineCap="round设置或返回线条的结束端点样式,

lineJoin ="round" 设置或返回两条线相交时,所创建的拐角类型

我们把路径的端点和连接点都设置为圆形

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
   context.lineCap="round";
context.lineJoin="round";
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
   context.lineTo(150,100) ;
  context.moveTo(101,10) ; 
  context.lineTo(101,50) ;
 
  context.stroke();

我们绘制的线条就是折线连接处和端点为椭圆了。

3.画布矩形和圆形绘制处理

通过上面线条绘制,我们其实也可以拼出来矩形,那太麻烦了,我们不用想,api肯定会提供这种矩形绘制处理

我们绘制矩形:

rect(x,y,width,height)  绘制矩形(无填充)

绘制的处理就是描边的处理,类似div边框的设置,当然还有填充的处理,就是div背景设置

 var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
   context.rect(100,100,200,200);
 
  context.stroke();

api还为我们提供了集成方法,创建矩形和绘制进行一起处理:

strokeRect(x,y,width,height 绘制矩形(无填充)

ar canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
   context.strokeRect(100,100,200,200);

4各参数,分别是横坐标,纵坐标和宽度高度值

我们绘制圆形:

arc(x,y,r,sangle,eangle,counterclockwise) 创建弧/曲线(用于创建圆形或部分圆)

参数表示:圆心横从坐标,开始弧度,结束弧度,是顺时针(true)还是逆时针(false)默认顺时针

要用弧度参数,2pi=360度

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
   context.arc(100,100,50,0,2*Math.PI)
 
  context.stroke();

在js中,获取pi值是通过Math对象的PI属性获取

我们可能要绘制半圆:

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
   context.arc(100,100,50,0,1*Math.PI)
 
  context.stroke();

现在是顺时针绘制,我们在绘制90度的圆弧,就知道绘制圆形的开始坐标位置了

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
   context.arc(100,100,50,0,Math.PI/2)
 
  context.stroke();

得出圆心的水平右侧的坐标就是开始点

我们想要得到闭合的圆弧,我们有开始和结束路径的方法,我们知道使用后会首尾连接,我们会致收尾连接的圆弧:

 var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
  context.beginPath();
   context.arc(100,100,50,0,Math.PI)
 context.closePath();
  context.stroke();

我们成功绘制出相连的圆弧。

4.画布弧线绘制处理

我们在使用moveto和lineto绘制线条时,可能希望连接位置是圆弧,和操场的跑道一样,

arcTo(x1,y1,x2,y2,r) 创建两切线之间的弧/曲线

参数x1 y1表示圆弧起点,x2 y2 表示结束,r为半径

 var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
  context.moveTo(20,20);           // 创建开始点
context.lineTo(100,20);          // 创建水平线
context.arcTo(150,20,150,70,50); // 创建弧
context.lineTo(150,120);         // 创建垂直线
context.stroke();

我们其实路径是20 20,下一步连接到100 20,然后开始绘制弧形,连接到150 20绘制为半径为50的弧度,然后连接到150 120绘制为半径为50的弧度,最后连接到150 120绘制为半径为50的弧度;

我们得出,调用一次arcto就会出现三个弧线。

5.画布贝塞尔曲线绘制处理

我们在ps里面用钢笔工具绘制路径的时候,点好起点和终点。中间会出现2个控制线,调节中间的弧形,贝塞尔就是这个处理

相关介绍:我们在css3的过渡效果和动画效果都是贝塞尔的处理,执行效果函数设置。那里面只有2个坐标,其实贝塞尔包含4个坐标,开始坐标,结束坐标,2个控制坐标。css里面的开始和结束已经被固定了,我们拿过渡效果举例,他的起始坐标就是开始的css某个属性效果设置,结束坐标就是变化结束属性效果的设置,css里面设置成0,0和1,1作为起始和终止坐标值,所以只需要设置控2制点坐标就好了,介于0和1之间。但是我们在画布上4个坐标都要设置,他的位置是自定义的,起始和终点。

quadraticCurveTo(kx1,ky1,kx2,ky2,ex,ey)  创建二次贝塞尔曲线

三组参数,前2组是控制点坐标,最后是结束点,我们的起始点事通过moveto创建的。

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =10;
  context.strokeStyle ="#000";
  context.moveTo(20,20);           // 创建开始点
  context.bezierCurveTo(20,100,200,100,200,20)

  context.stroke();

6.画布文字绘制处理

strokeText(text,x,y)  在画布上绘制文本(无填充)

参数意义,第一个表示要绘制的文本内容,x,y表示绘制文本的起始坐标

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.lineWidth =2;
  context.strokeStyle ="#000";
  context.strokeText("2",200,200) 

  context.stroke();

我css一样还有文本对齐方式和大小,什么字的设置,大家可以对着api添加

7.画布填充效果处理

填充和绘制的区别就是描边还是背景,我们对照上面的处理作出对已填充绘制

fill()  填充当前绘图(路径)

fillStyle 设置或返回用于填充绘画的颜色、渐变或模式

fillRect()  绘制“被填充”的矩形

arc() 创建弧/曲线(用于创建圆形或部分圆)

fillText()  在画布上绘制“被填充的”文本

填充矩形:

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#ffa";
  context.fillRect(100,100,200,200);

填充圆形:

 var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#ffa";
  context.arc(100,100,50,0,2*Math.PI);
  context.fill();

填充文本:

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#ffa";
  context.fillText("2",200,200)

各个方法参数几乎不变,不过是绘制换成了fill。

8.画布绘制图片

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  var img=new Image();
  img.src="calend.jpg";
  img.onload=function(){context.drawImage(img,100,100,30,30);}

绘制图片的第一个参数是img对象,下面依次是显示的坐标和宽高设置;

我们通过Image()可以创建img对象,赋值src属性让图片有资源,load事件保证图片加载完成后执行绘制

除了绘制图片外,第一个参数img也可以用video对象,把video视频正在播放的图片绘制到canvas画布上。

四、canvas的高级API使用

针对其他api的使用,我们结合事件处理。

1.清除画布操作

我们给页面添加一个清空按钮,点击执行清除操作,

我们先在画布绘制一些矩形和圆形还有线条,添加清除按钮,在js代码获取按钮:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  
#myCanvas{background:#ccc;}
</style>
<title>demo</title>
</head>
<body> 
  <canvas id="myCanvas" height="500" width="500"></canvas>
  <input type="button" value="清除" id="clear">
</body>
<script type="text/javascript"> 
window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  var clear=document.getElementById("clear");
  context.lineWidth =5;
  context.strokeStyle ="#000";
  context.strokeRect(100,100,200,200);
  context.arc(300,300,50,0,2*Math.PI);
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
  context.stroke();

};
  
</script>
</html>
<!-- 

 -->

canvas的api提供的清除操作接口:

clearRect(x,y,width,height) 在给定的矩形内清除指定的像素(清除位置和清除的宽高);

我们点击按钮整个画布清除,所以位置是0,0,宽高就是画布宽高,我们加入如下事件处理代码;

clearhandle.onclick=function(){
    context.clearRect(0,0,canvas.width,canvas.height)
  };

2.画布变换处理

1.对画布做缩放处理:

scale(calewidth,scaleheight) 缩放当前绘图至更大或更小

对水平设置缩放比例,对垂直设置缩放比例

我们看到有缩放,旋转,移动等,和我们css3提供的2d变换处理几乎一样,我们分别测试,

我们接着上面代码加入缩放处理:

window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  var clearhandle=document.getElementById("clear");
  context.lineWidth =5;
  context.strokeStyle ="#000";
  
  context.scale(0.8,1.2);
  context.strokeRect(100,100,200,200);
  context.arc(300,300,50,0,2*Math.PI);
  context.moveTo(10,10) ; 
  context.lineTo(10,50) ;
  context.stroke();

  clearhandle.onclick=function(){
    context.clearRect(0,0,canvas.width,canvas.height)
  };
  

  
};

我们设置缩放后,明显对后面绘制的图形造成了影响。

2.对画布做旋转处理:

rotate(angle)  旋转当前绘图,弧度单位

我们把缩放代码替换为旋转:

context.rotate(Math.PI/4);

2.对画布做移动处理:

translate(x,y) 重新映射画布上的 (0,0) 位置

context.translate(100,100);

3.画布状态的保存和释放

save()  保存当前环境的状态

restore() 返回之前保存过的路径状态和属性

调用save就会保存之前的画布状态,调用restore释放之前状态。

我们为啥要保存处理,我们上面使用了变换的操作,知道那种操作都是对画布的破坏性处理,只要使用了变化的而处理,后面绘制都会受到变换后的影响,所以我们在变化处理前进行画布为破坏前保存,然后做破坏处理,处理后,再释放到破坏前画布状态,我们就可以保证变换后的处理不受破坏影响。

我们要这样处理一段绘制:

  1. 正常画布绘制一个圆

  2. 画布移动100px后,绘制一个矩形

  3. 正常画布绘制一个矩形,位置大小同2,但是2处理做了移动,所以正常不会覆盖

我们不用save和restore的处理看效果:

window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  var clearhandle=document.getElementById("clear");
  context.lineWidth =5;
  context.strokeStyle ="#000";


  context.arc(300,300,50,0,2*Math.PI);
  
  context.translate(100,100);
  context.strokeRect(100,100,200,200);
  
  context.strokeRect(100,100,200,200);
  context.stroke();

  clearhandle.onclick=function(){
    context.clearRect(0,0,canvas.width,canvas.height)
  };
  

  
};

只有一个矩形,这是不正确的,我们这次在变换前save,在绘制第二个矩形restore

var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  var clearhandle=document.getElementById("clear");
  context.lineWidth =5;
  context.strokeStyle ="#000";


  context.arc(300,300,50,0,2*Math.PI);
  context.save();
  context.translate(100,100);
  context.strokeRect(100,100,200,200);
  context.restore();
  context.strokeRect(100,100,200,200);
  context.stroke();

  clearhandle.onclick=function(){
    context.clearRect(0,0,canvas.width,canvas.height)
  };

三个形状出现,我们也得出这一组方法就是为了对破坏前画布存储的处理。

4.画布转为图片

既然是画布,那就是绘图,如果只是看不能用的图,让人是寒心的,我们队画好的图

1.显示在html页面里

2.后台接收,保存

canvas.toDataURL("image/png")  转为图片,以base64的形式保存

我们可以看到,这个处理是基于canvas的不是基于画布,这是要注意的,我们传入的参数指定生成图片,格式是png,支持最好,这个方法返回的是一个图片地址,我们通过img的src属性可以看见。

我们在页面加入生成图片按钮,加入一个img标签,代码如下:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  
#myCanvas{background:#ccc;}
</style>
<title>demo</title>
</head>
<body> 
  <canvas id="myCanvas" height="500" width="500"></canvas>
  <input type="button" value="清除" id="clear">
  <input type="button" value="生成图片" id="newimg">
  <img src="" alt="我是canvas生成的图片" id="hasimg">
</body>
<script type="text/javascript"> 
window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  var clearhandle=document.getElementById("clear");
  context.lineWidth =5;
  context.strokeStyle ="#000";


  context.arc(300,300,50,0,2*Math.PI);
  context.save();
  context.translate(100,100);
  context.strokeRect(100,100,200,200);
  context.restore();
  context.strokeRect(100,100,200,200);
  context.stroke();

  clearhandle.onclick=function(){
    context.clearRect(0,0,canvas.width,canvas.height)
  };

  var newimghandle=document.getElementById("newimg");
  var hasimg=document.getElementById("hasimg");
  newimghandle.onclick=function(){
    var imgsrc=canvas.toDataURL("image/png");
    hasimg.src=imgsrc;
  };
  

  
};
  
</script>
</html>

5.其他说明

当然还有阴影和渐变的设置和处理,大家可以看一看,不在实例处理。

五、canvas的实用API使用

‍‍我们也可以大概看出,没有介绍的api,我先放在下面:‍‍

getImageData()  返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据

putImageData()  把图像数据(从指定的 ImageData 对象)放回画布上

globalCompositeOperation  设置或返回新图像如何绘制到已有的图像上

这几个就比较厉害了,一些交互实例中,这三个api无疑是大拿级别的,这些的使用我们必须结合处理,我们一次实例演示。

我们放弃上面所有的代码,下面针对这些属性,重新绘制实例处理:

1.静态结构搭建

我们创建一个基本结构,整体代码如下:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  
</style>
<title>demo</title>
</head>
<body> 
  <canvas id="myCanvas" height="200" width="300"></canvas>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000"
  context.fillRect(0,0,300,200);
  
  
};
  
</script>
</html>

我们创建canvas,获取canvas,返回画布,并且填充黑色矩形,整个覆盖画布。

2.鼠标在画布上按下后移动时绘制圆形

这个操作类似于我们以前做的拖拽效果,也要设置状态变量,我们做出组合事件的处理,代码如下:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  
</style>
<title>demo</title>
</head>
<body> 
  <canvas id="myCanvas" height="200" width="300"></canvas>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000"
  context.fillRect(0,0,300,200);
  var posi = document.getElementById("posi");  //获取canvas  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
    };
  };
  
};
  
</script>
</html>

我们在canvas进行按下移动操作时,我们在下面的div输出了移动事件对象的坐标值,我们为何要获取这些坐标值?这个坐标值就是我们事件绘制圆形的圆心坐标。

我们绘制的圆形:圆心就是事件对象的坐标值,半径自定义,弧度0~2pi

我们把后绘制与黑色矩形分分开,把圆形颜色设置为白色;我们下面在移动事件处理加入绘制处理,代码如下:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  
</style>
<title>demo</title>
</head>
<body> 
  <canvas id="myCanvas" height="200" width="300"></canvas>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,5,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
    };
  };
  
};
  
</script>
</html>

我们按下移动时,画布会不断绘制出白色圆形,我们得出,后绘制的图形会覆盖在原始绘制图形上面。

3.画布后绘制图形与原始绘制图形相交处理属性

globalCompositeOperation  设置或返回新图像如何绘制到已有的图像上

我们可以把一开始就在画布上的绘制处理叫做目标对象,后事件添加绘制叫做源对象。

我们看看这个属性的参数设置:

source-over 默认值,后绘制覆盖已有,我们上面已经验证

destination-out  后绘制与已有绘制相交部分透明

其他我就不介绍了,大家看具体api

我们把globalCompositeOperation ="destination-out ", 处理,

我们代码修改添加如下:

window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  context.globalCompositeOperation ="source-out ",
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,5,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
    };
  };
  
};

我们看不出来什么,透明下面看见也是白色,我们html结构进行调整:

我们把canvas放在一个父容器div内部,与canvas同级添加一个div,都相对父容器定位,canvas的层深大于兄弟元素,盖住操作:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  .box{position: relative;; width: 300px;height: 200px;}
  .box .chi{ position: absolute;; left: 0px;top: 0px;width: 300px;height: 200px;line-height: 200px;text-align: center;font-size: 50px;z-index: 9;}
  .box #myCanvas{position: absolute;; left: 0px;top: 0px;z-index: 99;cursor: pointer;}
  </style>
<title>demo</title>
</head>
<body> 

  <div class="box">
    <canvas id="myCanvas" height="200" width="300"></canvas>
    <div class="chi">一等奖</div>
  </div>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  context.globalCompositeOperation ="destination-out";
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,5,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
    };
  };
  
};
  
</script>
</html>

canvas在div存放“一等奖”的上面,我们在canvas上按下移动时,绘制内容与已有绘制相交变为透明,canvas下面的元素就会显示出来,所有我们 就可以看见canvas下面的div了。

4.生成随机文字

我们通过上面绘制相交属性设置为透明,我们可以看到下面的内容,我们下面div的文字如果不是固定,是随机出现的是不是就很类似刮刮卡了?

我们使用js提供的下面处理:

Math.random() 生成0-1之间随机数

Math.floor() 向下获取整数

我要得到0-3之间的整数,我们怎么处理?

有个规律,获取几到几,就把Math.random() *n,然后向下取整,代码如下:

alert(Math.floor(Math.random()*4))

我们n此刷新测试,发现弹出数字是0 1 2 3,满足我们的要求,我们奖项多个(一等奖,二等奖,三等奖,未中奖);

我们存入数组,然后alert输出:

var con=['一等奖','二等奖','三等奖','未中奖'];
  alert(con[Math.floor(Math.random()*4)]);

我们把alert的内容赋值给div就好了,这样我们每次就出随机赋值,代码修改如下:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  .box{position: relative;; width: 300px;height: 200px;}
  .box .chi{ position: absolute;; left: 0px;top: 0px;width: 300px;height: 200px;line-height: 200px;text-align: center;font-size: 50px;z-index: 9;}
  .box #myCanvas{position: absolute;; left: 0px;top: 0px;z-index: 99;cursor: pointer;}
  </style>
<title>demo</title>
</head>
<body> 

  <div class="box">
    <canvas id="myCanvas" height="200" width="300"></canvas>
    <div class="chi" id="conb"></div>
  </div>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var con=['一等奖','二等奖','三等奖','未中奖'];
  var conb= document.getElementById("conb"); 
  conb.innerHTML=con[Math.floor(Math.random()*4)];
  var arcr=10;

  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  context.globalCompositeOperation ="destination-out";
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,arcr,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
    };
  };
  
};
  
</script>
</html>

我们每次刷新后刮去画布,我们把移动绘制圆形的半径提出,用自定义变量处理,方便更改。

4.获取画布内容的rgba值

我们在刮去时,肯定不可能全部刮完,在看到文字时就停止了刮去,同样也会alert提示我刮去出来看见的内容。

我们做个假设,我们在刮去时,黑色区域越来越小,如果我们可以判断出黑色区域占总区域比例,当不足20%应该会看见文字,弹出奖项值。

getImageData(x,y,w,h)  返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据

返回图片数据对象,我们调用

getImageData(x,y,w,h) .data 属性可以返回所有像素分析,方法会把画布按1px分解分析,我们的画布是300*200.那么就会分析到300*200个结果。

我们输出调用data属性后的长度是不是300*200=60000;

var imgdata=context.getImageData(0,0,300,200);
  alert(imgdata.data.length)

返回分析数组长度是240000;是猜想的4倍,我们查看api可知,针对每一像素,都会进行RGBA的分析,我们实例是60000像素,那么1px返回4个值,240000是对的。

我们可以把图片数据对象的数据结果每4个看成一组,做出单位像素颜色的判断分析,我们的判断代码应该放在抬起的事件内部,判断剩余黑色比例,也就是数据对象的数据结果rgba为0 0 0 255在总数组的出现比例。

我们在抬起事件,把一维结果数组先每4个一组,形成一个按rgba为基准的新数组(分析数组,按照rgba划分得到):

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  .box{position: relative;; width: 300px;height: 200px;}
  .box .chi{ position: absolute;; left: 0px;top: 0px;width: 300px;height: 200px;line-height: 200px;text-align: center;font-size: 50px;z-index: 9;}
  .box #myCanvas{position: absolute;; left: 0px;top: 0px;z-index: 99;cursor: pointer;}
  </style>
<title>demo</title>
</head>
<body> 

  <div class="box">
    <canvas id="myCanvas" height="200" width="300"></canvas>
    <div class="chi" id="conb"></div>
  </div>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var con=['一等奖','二等奖','三等奖','未中奖'];
  var conb= document.getElementById("conb"); 
  conb.innerHTML=con[Math.floor(Math.random()*4)];
  var arcr=10;

  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  
  
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  context.globalCompositeOperation ="destination-out";
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,arcr,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
      var imgdata=context.getImageData(0,0,300,200);
      var len=imgdata.data.length;
      var rgbaarr=[];
      for(var i=0;i<len;i+=4){
        var temp=imgdata.data[i]+" "+imgdata.data[i+1]+" "+imgdata.data[i+2]+" "+imgdata.data[i+3];
        rgbaarr.push(temp);
      };
      console.log(rgbaarr);

      
    };
  };
  
};
  
</script>
</html>

我们把返回的数组按照rgba4个一组,我们输出新数组,得到了分析数组。

这时候我们只要判断出分析数组中出现,“0 0 0 255”的个数和占总像素个数(60000)的比例。

我们创建一个方法,返回分析数组中出现0 0 0 255的个数

function percent(obj,arr){
    var count=0;
    for(var i=0;i<arr.length;i++){
      if(arr[i]==obj){count+=1;}
    };
    return count;
  };

obj表示0 0 0 255,arr就是分析后数组;

我们返回比例,在抬起事件中,代码如下:

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  .box{position: relative;; width: 300px;height: 200px;}
  .box .chi{ position: absolute;; left: 0px;top: 0px;width: 300px;height: 200px;line-height: 200px;text-align: center;font-size: 50px;z-index: 9;}
  .box #myCanvas{position: absolute;; left: 0px;top: 0px;z-index: 99;cursor: pointer;}
  </style>
<title>demo</title>
</head>
<body> 

  <div class="box">
    <canvas id="myCanvas" height="200" width="300"></canvas>
    <div class="chi" id="conb"></div>
  </div>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var con=['一等奖','二等奖','三等奖','未中奖'];
  var conb= document.getElementById("conb"); 
  conb.innerHTML=con[Math.floor(Math.random()*4)];
  var arcr=10;

  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  
  
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  context.globalCompositeOperation ="destination-out";
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,arcr,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
      var imgdata=context.getImageData(0,0,300,200);
      var len=imgdata.data.length;
      var rgbaarr=[];
      for(var i=0;i<len;i+=4){
        var temp=imgdata.data[i]+" "+imgdata.data[i+1]+" "+imgdata.data[i+2]+" "+imgdata.data[i+3];
        rgbaarr.push(temp);
      };
      alert(percent("0 0 0 255",rgbaarr)/rgbaarr.length);


      
    };
  };
  function percent(obj,arr){
    var count=0;
    for(var i=0;i<arr.length;i++){
      if(arr[i]==obj){count+=1;}
    };
    return count;
  };
  
};
  
</script>
</html>

我们在抬起事件调用方法,除以总个数,获取了黑色区域所占百分比了。我们的参考比例是不足20%。也就是黑色不足0.2,我们在抬起事件加入这个比例判断,如果满足弹出奖项内容。

<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8" /> 
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style type="text/css">
 *{ margin:0; padding:0;}  
  .box{position: relative;; width: 300px;height: 200px;}
  .box .chi{ position: absolute;; left: 0px;top: 0px;width: 300px;height: 200px;line-height: 200px;text-align: center;font-size: 50px;z-index: 9;}
  .box #myCanvas{position: absolute;; left: 0px;top: 0px;z-index: 99;cursor: pointer;}
  </style>
<title>demo</title>
</head>
<body> 

  <div class="box">
    <canvas id="myCanvas" height="200" width="300"></canvas>
    <div class="chi" id="conb"></div>
  </div>
  <div id="posi"></div>
</body>
<script type="text/javascript"> 
window.onload=function(){
  var con=['一等奖','二等奖','三等奖','未中奖'];
  var conb= document.getElementById("conb"); 
  conb.innerHTML=con[Math.floor(Math.random()*4)];
  var arcr=10;

  var canvas = document.getElementById("myCanvas");  //获取canvas   
  var context = canvas.getContext('2d');  //canvas追加2d画图anvas = document.getElementById("canvas");  
  context.fillStyle="#000";
  context.fillRect(0,0,300,200);
  
  
  var posi = document.getElementById("posi");  
  var isdown=false;
  canvas.onmousedown=function(event){
    isdown=true;
  };
  context.fillStyle="#fff";
  context.globalCompositeOperation ="destination-out";
  canvas.onmousemove=function(event){
    if(isdown){
      posi.innerHTML="坐标x:"+event.layerX+";"+"坐标u:"+event.layerY;
      context.arc(event.layerX,event.layerY,arcr,0,2*Math.PI);
      context.fill();
    };
  };
  canvas.onmouseup=function(event){
    if(isdown){
      isdown=false;
      var imgdata=context.getImageData(0,0,300,200);
      var len=imgdata.data.length;
      var rgbaarr=[];
      for(var i=0;i<len;i+=4){
        var temp=imgdata.data[i]+" "+imgdata.data[i+1]+" "+imgdata.data[i+2]+" "+imgdata.data[i+3];
        rgbaarr.push(temp);
      };
      if(percent("0 0 0 255",rgbaarr)/rgbaarr.length<0.2){
        alert(con[Math.floor(Math.random()*4)]);
      };


      
    };
  };
  function percent(obj,arr){
    var count=0;
    for(var i=0;i<arr.length;i++){
      if(arr[i]==obj){count+=1;}
    };
    return count;
  };
  
};
  
</script>
</html>

我们刮去画布,当剩下黑色不足0.2,我们就看到弹出了奖项,这个比例是可以随便设置的。

六、canvas总结

我们通过getImageData(x,y,w,h) 获取选取区域图片数据对象,返回他的数据属性,获取选取区域rgba具体分析值,我们做出判断就可以实现动态弹出的处理。

通过globalCompositeOperation ="destination-out ", 处理,相交透明看到下面的文字。

这2个api就是刮刮卡效果的重要处理接口。


关于刮刮效果的一个实例处理:http://www.oschina.net/code/snippet_2352644_49815 

其他canvas的实例处理:http://www.oschina.net/code/snippet_2352644_49814 





你可能感兴趣的:(html5,canvas)