1.LAMP一键安装脚本
https://teddysun.com/410.html
12.Ubuntu/Debian/Centos一键安装FTP脚本
http://vpsxyz.com/archives/21
13.网速测试
主要包括国内的一些节点进行专项测试,电信、联通、移动
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/superspeed/superspeed.sh“;chmod +x superspeed.sh;sudo bash ./superspeed.sh
14.html5网速测试
https://down.vpsmm.com/php/speedtest.zip
需要php环境,下载后解压即可运行,无须安装
15.SS一键包4版本
一键安装ss服务器端4合1版本,自主选择:python版、R版(推荐)、go版、libev版(省内存)。
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/ss/ss_all.sh“;chmod +x ss_all.sh;sudo bash ./ss_all.sh
16.锐速一键包
一键安装锐速破解全功能版,不支持openvz架构,来自91yun
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/serverspeeder/serverspeeder.sh“;chmod +x serverspeeder.sh;sudo bash ./serverspeeder.sh
另一位爱好者开发的锐速一键安装脚本,开发:https://github.com/0oVicero0/serverSpeeser_Install
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/serverspeeder/serverspeeder_appex.sh“;chmod +x serverspeeder_appex.sh;sudo bash ./serverspeeder_appex.sh
17.FS/finalspeed一键包
一键安装finalspeed功能,支持全系架构
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/finalspeed/finalspeed.sh“;chmod +x finalspeed.sh;sudo bash ./finalspeed.sh
18.mysql一键备份脚本
每天自动备份MYSQL及打包网站目录
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/AutuBackupToFtp/AutoBackupToFtp.sh“;chmod +x AutoBackupToFtp.sh;sudo bash ./AutoBackupToFtp.sh
19.transfer.sh 中文一键安装脚本
wget -N –no-check-certificate “https://raw.githubusercontent.com/WhosYourFathe/UsefulScripts/master/transfer/transfer.sh“;chmod +x transfer.sh;sudo bash ./transfer.sh
我们以Java Web为例,来搭建一个简单的电商系统,看看这个系统可以如何一步步演变。
该系统具备的功能:
阶段一、单机构建网站
网站的初期,我们经常会在单机上跑我们所有的程序和软件。此时我们使用一个容器,如Tomcat、Jetty、Jboss,然后直接使用JSP/Servlet技术,或者使用一些开源的框架如Maven + Spring + Struts + Hibernate、Maven + Spring + Spring MVC + Mybatis。最后再选择一个数据库管理系统来存储数据,如MySQL、SqlServer、Oracle,然后通过JDBC进行数据库的连接和操作。
把以上的所有软件包括数据库、应用程序都装载同一台机器上,应用跑起来了,也算是一个小系统了。此时系统结果如下:
阶段二、应用服务器与数据库分离
随着网站的上线,访问量逐步上升,服务器的负载慢慢提高,在服务器还没有超载的时候,我们应该就要做好准备,提升网站的负载能力。假如我们代码层面已难以优化,在不提高单台机器的性能的情况下,采用增加机器是一个不错的方式,不仅可以有效地提高系统的负载能力,而且性价比高。
增加的机器用来做什么呢?此时我们可以把数据库服务器和Web服务器拆分开来,这样不仅提高了单台机器的负载能力,也提高了容灾能力。
应用服务器与数据库分开后的架构如下图所示:
阶段三、应用服务器集群
随着访问量继续增加,单台应用服务器已经无法满足需求了。在假设数据库服务器没有压力的情况下,我们可以把应用服务器从一台变成了两台甚至多台,把用户的请求分散到不同的服务器中,从而提高负载能力。而多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。著名的做故障切换的软件有KeepAlived,KeepAlived是一个类似于Layer3、4、7交换机制的软件,他不是某个具体软件故障切换的专属品,而是可以适用于各种软件的一款产品。KeepAlived配合上ipvsadm又可以做负载均衡,可谓是神器。
我们以增加了一台应用服务器为例,增加后的系统结构图如下:
系统演变到这里,将会出现下面四个问题:
针对以上问题,常用的解决方案如下:
1、负载均衡的问题
一般以下有5种解决方案:
1、HTTP重定向
HTTP重定向就是应用层的请求转发。用户的请求其实已经到了HTTP重定向负载均衡服务器,服务器根据算法要求用户重定向,用户收到重定向请求后,再次请求真正的集群
2、DNS域名解析负载均衡
DNS域名解析负载均衡就是在用户请求DNS服务器,获取域名对应的IP地址时,DNS服务器直接给出负载均衡后的服务器IP。
3、反向代理服务器
在用户的请求到达反向代理服务器时(已经到达网站机房),由反向代理服务器根据算法转发到具体的服务器。常用的Apache,Nginx都可以充当反向代理服务器。
4、IP层负载均衡
在请求到达负载均衡器后,负载均衡器通过修改请求的目的IP地址,从而实现请求的转发,做到负载均衡。
5、数据链路层负载均衡
在请求到达负载均衡器后,负载均衡器通过修改请求的MAC地址,从而做到负载均衡,与IP负载均衡不一样的是,当请求访问完服务器之后,直接返回客户。而无需再经过负载均衡器。
2、集群调度转发算法
1、rr轮询调度算法
顾名思义,轮询分发请求。
2、wrr加权调度算法
我们给每个服务器设置权值Weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。
3、sh原地址散列算法
提取用户IP,根据散列函数得出一个key,再根据静态映射表,查处对应的value,即目标服务器IP。过目标机器超负荷,则返回空。
4、dh目标地址散列算法
原理同上,只是现在提取的是目标地址的IP来做哈希。
5、lc最少连接算法
优先把请求转发给连接数少的服务器。
6、wlc加权最少连接算法
在lc的基础上,为每台服务器加上权值。算法为:(活动连接数 * 256 + 非活动连接数) ÷ 权重,计算出来的值小的服务器优先被选择。
7、sed最短期望延迟算法
其实sed跟wlc类似,区别是不考虑非活动连接数。算法为:(活动连接数 +1 ) * 256 ÷ 权重,同样计算出来的值小的服务器优先被选择。
8、nq永不排队算法
改进的sed算法。我们想一下什么情况下才能“永不排队”,那就是服务器的连接数为0的时候,那么假如有服务器连接数为0,均衡器直接把请求转发给它,无需经过sed的计算。
9、LBLC基于局部性最少连接算法
负载均衡器根据请求的目的IP地址,找出该IP地址最近被使用的服务器,把请求转发之。若该服务器超载,最采用最少连接数算法。
10、LBLCR带复制的基于局部性最少连接算法
负载均衡器根据请求的目的IP地址,找出该IP地址最近使用的“服务器组”,注意,并不是具体某个服务器,然后采用最少连接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最少连接数算法,在集群的非本服务器组的服务器中,找出一台服务器出来,加入本服务器组,然后把请求转发。
3、集群请求返回模式问题
1、NAT
负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再重新返回给用户。
2、DR
负载均衡器接收用户的请求,转发给具体服务器,服务器出来玩请求后直接返回给用户。需要系统支持IP Tunneling协议,难以跨平台。
3、TUN
同上,但无需IP Tunneling协议,跨平台性好,大部分系统都可以支持。
4、集群Session一致性问题
1、Session Sticky
Session sticky就是把同一个用户在某一个会话中的请求,都分配到固定的某一台服务器中,这样我们就不需要解决跨服务器的session问题了,常见的算法有ip_hash算法,即上面提到的两种散列算法。
2、Session Replication
Session replication就是在集群中复制session,使得每个服务器都保存有全部用户的session数据。
3、Session数据集中存储
Session数据集中存储就是利用数据库来存储session数据,实现了session和应用服务器的解耦。
4、Cookie Base
Cookie base就是把Session存在Cookie中,由浏览器来告诉应用服务器我的session是什么,同样实现了session和应用服务器的解耦。
值得一提的是:
解决了以上的问题之后,系统的结构如下:
阶段四、数据库读写分离化
上面我们总是假设数据库负载正常,但随着访问量的的提高,数据库的负载也在慢慢增大。那么可能有人马上就想到跟应用服务器一样,把数据库一份为二再负载均衡即可。
但对于数据库来说,并没有那么简单。假如我们简单的把数据库一分为二,然后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会造成两台数据库数据不统一的问题。那么对于这种情况,我们可以先考虑使用读写分离和主从复制的方式。
读写分离后的系统结构如下:
这个结构变化后也会带来两个问题:
解决方案:
阶段五、用搜索引擎缓解读库的压力
数据库做读库的话,常常对模糊查找力不从心,即使做了读写分离,这个问题还未能解决。以我们所举的交易网站为例,发布的商品存储在数据库中,用户最常使用的功能就是查找商品,尤其是根据商品的标题来查找对应的商品。对于这种需求,一般我们都是通过like功能来实现的,但是这种方式的代价非常大,而且结果非常不准确。此时我们可以使用搜索引擎的倒排索引来完成。
搜索引擎具有的优点:它能够大大提高查询速度和搜索准确性。
引入搜索引擎的开销
搜索引擎并不能替代数据库,它解决了某些场景下的精准、快速、高效的“读”操作,是否引入搜索引擎,需要综合考虑整个系统的需求。
引入搜索引擎后的系统结构如下:
阶段六、用缓存缓解读库的压力
常用的缓存机制包括页面级缓存、应用数据缓存和数据库缓存。
应用层和数据库层的缓存
随着访问量的增加,逐渐出现了许多用户访问同一部分热门内容的情况,对于这些比较热门的内容,没必要每次都从数据库读取。我们可以使用缓存技术,例如可以使用Google的开源缓存技术Guava或者使用Memecahed作为应用层的缓存,也可以使用Redis作为数据库层的缓存。
另外,在某些场景下,关系型数据库并不是很适合,例如我想做一个“每日输入密码错误次数限制”的功能,思路大概是在用户登录时,如果登录错误,则记录下该用户的IP和错误次数,那么这个数据要放在哪里呢?假如放在内存中,那么显然会占用太大的内容;假如放在关系型数据库中,那么既要建立数据库表,还要简历对应的Java bean,还要写SQL等等。而分析一下我们要存储的数据,无非就是类似{ip:errorNumber}这样的key:value数据。对于这种数据,我们可以用NOSQL数据库来代替传统的关系型数据库。
页面缓存
除了数据缓存,还有页面缓存。比如使用HTML5的localstroage或者Cookie。除了页面缓存带来的性能提升外,对于并发访问且页面置换频率小的页面,应尽量使用页面静态化技术。
值得一提的是:
缓存集群的调度算法不同与上面提到的应用服务器和数据库。最好采用一致性哈希算,这样才能提高命中率。
加入缓存后的系统结构如下:
阶段七、数据库水平拆分与垂直拆分
我们的网站演进到现在,交易、商品、用户的数据都还在同一个数据库中。尽管采取了增加缓存和读写分离的方式,但随着数据库的压力继续增加,数据库数据量的瓶颈越来越突出,此时,我们可以有数据垂直拆分和水平拆分两种选择。
数据垂直拆分
垂直拆分的意思是把数据库中不同的业务数据拆分到不同的数据库中,结合现在的例子,就是把交易、商品、用户的数据分开。
优点:
缺点:
问题:
解决问题方案:
数据垂直拆分后的结构如下:
数据水平拆分
数据水平拆分就是把同一个表中的数据拆分到两个甚至多个数据库中。产生数据水平拆分的原因是某个业务的数据量或者更新量到达了单个数据库的瓶颈,这时就可以把这个表拆分到两个或更多个数据库中。
优点:
问题:
解决问题方案:
数据水平拆分后的结构如下:
阶段八、应用的拆分
按微服务拆分应用
随着业务的发展,业务越来越多,应用越来越大。我们需要考虑如何避免让应用越来越臃肿。这就需要把应用拆开,从一个应用变为俩个甚至更多。还是以我们上面的例子,我们可以把用户、商品、交易拆分开。变成“用户、商品”和“用户,交易”两个子系统。
拆分后的结构:
问题:
这样拆分后,可能会有一些相同的代码,如用户相关的代码,商品和交易都需要用户信息,所以在两个系统中都保留差不多的操作用户信息的代码。如何保证这些代码可以复用是一个需要解决的问题。
解决问题:
通过走服务化SOA的路线来解决频繁公共的服务。
走SOA服务化治理道路
为了解决上面拆分应用后所出现的问题,我们把公共的服务拆分出来,形成一种服务化的模式,简称SOA。
采用服务化之后的系统结构:
优点:
问题:
如何进行远程的服务调用?
解决方法:
可以通过下面的引入消息中间件来解决。
阶段九、引入消息中间件
随着网站的继续发展,的系统中可能出现不同语言开发的子模块和部署在不同平台的子系统。此时我们需要一个平台来传递可靠的,与平台和语言无关的数据,并且能够把负载均衡透明化,能在调用过程中收集并分析调用数据,推测出网站的访问增长率等等一系列需求,对于网站应该如何成长做出预测。开源消息中间件有阿里的Dubbo,可以搭配Google开源的分布式程序协调服务Zookeeper实现服务器的注册与发现。
引入消息中间件后的结构:
以上的演变过程只是一个例子,并不适合所有的网站,实际中网站演进过程与自身业务和不同遇到的问题有密切的关系,没有固定的模式。只有认真的分析和不断地探究,才能发现适合自己网站的架构。
上节
绘制文本:
可以在Canvas画布中进行文本的绘制,同时也可以指定绘制文本的字体、大小、对齐方式等,还可以进行文字的纹理填充等;
绘制文本涉及两个方法,分别为:
fillText(text,x,y,[maxwidth])方法:用填充方式绘制字符串;
strokeText(text,x,y,[maxwidth])方法:用轮廓方式绘制字符串;
这两个方法都接收4个参数:要绘制的文本字符串、x和y坐标、以及一个可选的maxwidth参数,表示显示文字时最大的宽度,可以防止文字溢出;
fillText()方法使用fillStyle属性绘制文本,而strokeText()以strokeStyle属性为文本描边;如:
context.fillText('零点程序员', 0, 50);
context.strokeText('零点程序员', 0, 100);
context.fillText('零点程序员', 0, 150, 30);
context.fillStyle="#00f";
context.fillText('零点程序员', 0, 200);
context.strokeStyle="#f00";
context.strokeText('零点程序员', 0, 250);
context.strokeText('零点程序员', 0, 250, 30);
示例:绘制包含数据说明的柱状图
context.fillStyle="white";
context.fillRect(0,0,canvas.width,canvas.height);
var data=[100, 50, 20, 30, 100];
var colors=[ "red","orange", "yellow","green", "blue"];
for(var i=0; i<data.length; i++){
var dt=data[i];
context.fillStyle=colors[i];
context.fillRect(25+i*50, 280-dt*2, 50, dt*2);
}
context.fillStyle="black";
context.lineWidth=2;
context.beginPath();
context.moveTo(25,10);
context.lineTo(25,280);
context.lineTo(290,280);
context.stroke();
for(var i=0; i<6; i++){
context.fillText((5-i)*20 + "", 4, i*40+80);
context.beginPath();
context.moveTo(25, i*40+80);
context.lineTo(30, i*40+80);
context.stroke();
}
var labels=["JAN","FEB","MAR","APR","MAY"];
for(var i=0; i<5; i++)
context.fillText(labels[i], 40+ i*50, 300);
在进行文字绘制之前,可以先对该对象的有关文字绘制的属性进行设置,如:
这三个属性都有默认值,因此也不是必须显式去设置它们;
// 使用字体样式
context.font='italic bold 30px sans-serif';
context.textAlign="end";
context.textBaseline='middle';
context.fillText('零点网络', 150 , 300);
context.font="bold 14px Arial";
context.textAlign="center";
context.textBaseline="middle";
context.fillText("12", 100, 20);
再议textAlign属性,当值为start时,则x坐标表示的是文本左端的位置,如果设置为end,则x坐标表示的是文本右端的位置,如果设置为center,则x坐标表示的是文本的中间的位置,如:
// 默认为start
context.fillText('零点程序员', 100 , 200);
context.textAlign="start"; // 起点对齐
context.fillText('零点程序员', 100 , 220);
context.textAlign="center"; // 中间对齐
context.fillText('零点程序员', 100 , 240);
context.textAlign="end"; // 终点对齐
context.fillText('零点程序员', 100 , 260);
获取文字宽度:measureText(text)该方法使用要绘制的文本作为参数,返回一个TextMetrics对象,该对象有个最重要的width属性,表示使用当前指定的字体后,text参数中指定的文字的总文字宽度,如:
var txt="零点程序员";
var tm=context.measureText(txt);
console.log(tm); // TextMetrics
console.log(tm.width); // 50
measureText()方法利用font、textAlign和textBaseline的当前值计算指定文本的大小,如:
var txt="零点程序员";
context.fillText(txt, 10, 50);
var tm=context.measureText(txt);
console.log(tm); // TextMetrics
console.log(tm.width); // 50
context.fillStyle="#00f";
context.font="italic 20px san-serif";
var tm=context.measureText(txt);
console.log(tm.width); // 100
context.fillText(txt, 10, 100);
context.fillText(tm.width , tm.width + 10, 100);
context.font="bold 32px san-serif";
var tm2=context.measureText(txt);
console.log(tm2.width); // 160
context.fillStyle="purple";
context.fillText(txt, 10, 150);
context.fillText(tm2.width, tm2.width + 10, 150);
如:使用适当的大小绘制文本
var txt="零点程序员";
var fontSize=100;
context.font=fontSize + "px Arial";
while(context.measureText(txt).width > 140){
fontSize--;
context.font=fontSize + "px Arial";
}
context.fillText(txt, 50, 50);
context.fillText("字体大小是:" + fontSize + "px", 50, 100);
direction属性:用来在绘制文本时,描述当前文本方向的属性,可能的值:
context.font='48px serif';
context.fillText('zero!', 200, 50);
// context.direction='rtl';
context.fillText('zero!', 200, 130);
filter属性:滤镜,提供模糊、灰度等过滤效果的属性,类似于CSS filter属性,并且接受相同的函数;
context.filter="blur(5px)";
context.font="48px serif";
context.strokeText("大师哥王唯", 100, 100);
Chrome还定义了多个有关字体的属性:
示例:绘制带有文字的饼形图
function PieChart (context){
this.context=context || document.getElementById("canvas").getContext("2d");
this.x=this.context.canvas.width/2 - 30;
this.y=this.context.canvas.height/2;
this.r=120;
this.outLine=20;
this.dataList=null;
}
PieChart.prototype={
constructor:PieChart,
init:function(dataList){
this.dataList=dataList || [{title:"默认",value:100}];
this.transformAngle();
this.drawPie();
},
drawPie:function(){
var startAngle=0,endAngle;
for(var i=0 ; i < this.dataList.length ; i++){
var item=this.dataList[i];
endAngle=startAngle + item.angle;
this.context.beginPath();
this.context.moveTo(this.x,this.y);
this.context.arc(this.x,this.y,this.r,startAngle,endAngle,false);
var color=this.context.strokeStyle=this.context.fillStyle=this.getRandomColor();
this.context.stroke();
this.context.fill();
this.drawPieTitle(startAngle,item.angle,color,item.title)
this.drawPieLegend(i,item.title);
startAngle=endAngle;
}
},
drawPieTitle:function(startAngle,angle,color,title){
var edge=this.r + this.outLine;
var edgeX=Math.cos(startAngle + angle / 2) * edge;
var edgeY=Math.sin(startAngle + angle / 2) * edge;
var outX=this.x + edgeX;
var outY=this.y + edgeY;
this.context.beginPath();
this.context.moveTo(this.x,this.y);
this.context.lineTo(outX,outY);
this.context.strokeStyle=color;
this.context.stroke();
var textWidth=this.context.measureText(title).width + 5;
var lineX=outX > this.x ? outX + textWidth : outX - textWidth;
this.context.lineTo(lineX,outY);
this.context.stroke();
this.context.font="15px KaiTi";
this.context.textAlign=outX > this.x ? "left" : "right";
this.context.textBaseline="bottom";
this.context.fillText(title,outX,outY);
},
drawPieLegend:function(index,title){
var space=10;
var rectW=40;
var rectH=20;
var rectX=this.x + this.r + 80;
var rectY=this.y + (index * 30);
this.context.fillRect(rectX,rectY,rectW,rectH);
// this.context.beginPath();
this.context.textAlign='left';
this.context.textBaseline='top';
this.context.fillStyle="#000";
this.context.fillText(title,rectX + rectW + space,rectY);
},
getRandomColor:function(){
var r=Math.floor(Math.random() * 256);
var g=Math.floor(Math.random() * 256);
var b=Math.floor(Math.random() * 256);
return 'rgb('+r+','+g+','+b+')';
},
transformAngle:function(){
var self=this;
var total=0;
this.dataList.forEach(function(item,i){
total +=item.value;
})
this.dataList.forEach(function(item,i){
self.dataList[i].angle=2 * Math.PI * item.value/total;
})
},
}
var data=[{value:20,title:"UI"},{value:26,title:"java"},
{value:20,title:"iOS"},{value:63,title:"H5"},{value:25,title:"Node"}]
var pie=new PieChart().init(data);
裁切路径:
在绘制图形的时候,如果只保留图形的一部分,可以使用裁切路径;
使用clip()方法,可以将当前创建的路径设置为当前剪切路径;
使用原理是:首先在画布内使用路径,只绘制该路径所包括区域内的图像;再使用clip()方法,该方法创建一个裁切路径,使用该路径对canvas画布设置一个裁剪区域;如:
context.arc(100, 100, 75, 0, Math.PI*2, false);
context.clip();
context.fillRect(0, 0, 100,100);
默认情况下,canvas 有一个与它自身一样大的裁切路径;
示例:
context.font="bold 60pt sans-serif";
context.lineWidth=2;
context.strokeStyle="#F00";
context.strokeText("零点程序员", 15, 330);
context.strokeRect(175,25,50,350);
context.beginPath();
context.moveTo(200, 50);
context.lineTo(350, 350);
context.lineTo(50, 350);
context.closePath();
context.clip();
context.lineWidth=10;
context.stroke();
context.fillStyle="#aaa";
context.fillRect(175,25,50,350);
context.fillStyle="#888";
context.fillText("零点程序员", 15, 330);
示例:绘制一个五角形裁切路径
var image=new Image();
image.src="images/1.png";
image.onload=function(){
createStar(context);
context.drawImage(image,-50,-150,300,300);
}
function createStar(context){
var dx=100, dy=0, s=150, dig=Math.PI / 5 * 4;
context.beginPath();
context.translate(100, 150);
for(var i=0; i<5; i++){
var x=Math.sin(i*dig);
var y=Math.cos(i*dig);
context.lineTo(dx+x*s, dy+y*s);
}
context.clip();
}
裁剪区域一旦设置好后,后续绘制的所有图形都使用这个裁切区域;如果要取消这个已经设置好的裁剪区域,由于没有重置裁切路径的方法,所以,需要使用绘制状态的保存与恢复功能;即通过save()和restore(),对之后绘制的图像取消裁剪区域;
示例:探照灯效果
<canvas id="myCanvas" width="400" height="400" style="border:3px double #996633;"></canvas>
<script>
var rot=10;
var canvas=document.getElementById('myCanvas');
var context=canvas.getContext('2d');
setInterval("draw()",100);
function draw(){
context.clearRect(0,0,400,400);
context.save();
context.fillStyle="black";
context.fillRect(0,0,400,400);
context.beginPath();
context.arc(rot, 200, 40, 0, Math.PI*2, true);
context.closePath();
context.fillStyle="white";
context.fill();
context.clip();
context.font="bold 45px 隶书";
context.textAlign="center";
context.textBaseline="middle";
context.fillStyle="#FF0000";
context.fillText("零点程序员大师哥",200,200);
context.restore();
rot=rot+10;
if (rot>400) rot=10;
}
</script>
示例:随机星星
context.fillRect(0,0,150,150);
context.translate(75,75);
context.beginPath();
context.arc(0,0,60,0,Math.PI*2,true);
context.clip();
var lingrad=context.createLinearGradient(0,-75,0,75);
lingrad.addColorStop(0, '#232256');
lingrad.addColorStop(1, '#143778');
context.fillStyle=lingrad;
context.fillRect(-75,-75,150,150);
for (var j=1; j<50; j++){
context.save();
context.fillStyle='#fff';
context.translate(75-Math.floor(Math.random()*150),
75-Math.floor(Math.random()*150));
drawStar(context, Math.floor(Math.random()*4)+2);
context.restore();
}
function drawStar(context, r){
context.save();
context.beginPath()
context.moveTo(r, 0);
for (var i=0; i<9; i++){
context.rotate(Math.PI/5); // 36度
if(i%2==0) {
context.lineTo((r/0.525731)*0.200811, 0);
} else {
context.lineTo(r, 0);
}
}
context.closePath();
context.fill();
context.restore();
}
绘制图像:
2D上下文内置了对图像(位图)的支持,可以读取本地以及网络中的图片,再将该图像的像素内容绘制(复制)在画布中;必要的时候,还可以对图片进行缩放和旋转;
使用drawImage()方法即可绘制图像;
drawImage(image,x,y):绘制图像,参数image为一个将要被绘制到画布上的源图片,x和y指定了待绘制图片的左上角的坐标;绘制的图像和原图大小相同;
绘制图像时,首先需要一个Image对象或一个<img>元素,如:
var image=document.images[0];
context.drawImage(image, 50, 50);
如果使用Image(),设定好image的src属性后,并不一定立刻就能把图像绘制完毕,如有时该图像来自网络的比较大的图像文件,就需要完全下载后才能绘制,所以需要在image的onload事件进行处理,此时就可以一边装载一边绘制了,如:
image=new Image();
image.src="images/1.jpg";
image.onload=function(){
drawImg(context,image);
}
function drawImg(context,image){
for(var i=0;i<7;i++){
context.drawImage(image,i*50,i*25,100,100);
}
}
示例:一个简单的线图
var img=new Image();
img.onload=function(){
context.drawImage(img,0,0);
context.beginPath();
context.moveTo(30,96);
context.lineTo(70,66);
context.lineTo(103,76);
context.lineTo(170,15);
context.stroke();
}
img.src='images/backdrop.png';
drawImage(image, x, y, w, h):使用w和h设置绘制的图像的大小,可以用来图像缩放;会绘制整个源图像;如:
context.drawImage(image, 50, 50, 200, 160);
按比例指定大小,如:
var image=document.images[0];
var w=image.width,
h=image.height;
var ratio=w / h;
var nw=200,
nh=nw / ratio;
context.drawImage(image, 50, 50, nw, nh);
示例:平铺图像:
var img=new Image();
img.onload=function(){
for (var i=0; i<4; i++){
for (var j=0; j<3; j++){
context.drawImage(img, j*50, i*38, 50, 38);
}
}
};
img.src='images/1.jpg';
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)方法:可以将画布中已绘制好的图像的全部或者局部区域复制到画布中的另一个位置上;sx和sy表示源图像的被复制区域的坐标;sw和sh表示源图像的被复制区域的宽和高;dx和dy表示复制后的目标图像的坐标;dw和dh表示复制后的目标图像的宽和高;
该方法可以只复制图像的局部,只要将sx和sy设为局部区域的起始坐标,将sw与sh设为局部区域的宽和高即可;该方法也可以用来缩放源图像,只要将dw和dh设为缩放后的宽和高即可;
如:局部放大复制到另外一个位置:
context.drawImage(image, 100, 100, 150, 100, 10, 10, 100, 50);
示例:相框:
context.drawImage(document.getElementById('source'),
33,71,104,124,21,20,87,104); // 图片
context.drawImage(document.getElementById('frame'),0,0);
示例:画廊
window.onload=function(){
document.body.style.backgroundColor="#4f191A";
var canvas, context;
var frameImg=new Image();
frameImg.src="images/picture_frame.png";
frameImg.onload=function(){
var imgsArr=["images/1.jpg","images/2.jpg","images/3.jpg","images/4.jpg","images/5.jpg","images/6.jpg","images/7.jpg","images/8.jpg",]
for (i=0; i<imgsArr.length; i++){
(function(i){
var img=new Image();
img.src=imgsArr[i];
img.onload=function(){
canvas=document.createElement('canvas');
canvas.setAttribute('width',132);
canvas.setAttribute('height',150);
document.body.appendChild(canvas);
context=canvas.getContext('2d');
context.drawImage(img,15,20,102,110);
context.drawImage(frameImg,0,0);
}
})(i);
}
}
}
除了使用image作为源图像之外,也可以传入一个<canvas>元素,这样,就可以把另一个画布内容绘制到当前画布上;除此之外,还可以是一个Video元素、ImageBitmap对象或使用dataURL嵌入的图像;
如,使用视频帧:
<canvas id="canvas" width="600" height="400"></canvas>
<video width="600" height="400" src="media/video.mp4" controls></video>
<script>
var video=document.querySelector("video");
var canvas=document.getElementById("canvas");
var interId;
var context=canvas.getContext("2d");
video.onplay=function(){
interId=setInterval(function(){
context.clearRect(0,0,600,400);
context.fillRect(0,0,600,400);
context.drawImage(video, 0, 70, 600, 440);
context.font="20px 微软雅黑";
context.strokeStyle="#999";
context.strokeText("零点程序员", 50, 50);
},16);
}
video.onpause=function(){
clearInterval(interId);
}
</script>
图像平铺:
所谓的平铺就是按一定比例缩小后的图像填满画布,有两种方法,一是使用drawImage方法:
var image=new Image();
image.src="images/1.jpg";
image.onload=function(){
drawImage(canvas,context,image);
}
function drawImage(canvas,context,image){
var scale=20;
var w=image.width / scale; var h=image.height / scale;
var numX=canvas.width / w; var numY=canvas.height / h;
for(var i=0;i<numX;i++){
for(var j=0;j<numY;j++){
context.drawImage(image,i*w,j*h,w,h);
}
}
}
第二种方法:可以使用context的createPattern(image, type)方法,参数type指定了重复的类型,其可能的值:no-repeat、repeat-x、repeat-y、repeat;创建完createPattern对象后,再赋给fillStyle即可;
var image=new Image();
image.src="images/1.jpg";
image.onload=function(){
var pattern=context.createPattern(image,"repeat");
context.fillStyle=pattern;
context.fillRect(0,0,400,300);
}
图形、图像的混合与组合(composite):
混合(合成)图像:
所谓图像混合(合成),是指使用某种数学公式将两幅图像混合在一起;从这个角度说,图像混合有些类似于对一幅图像使用一定的透明度后将其放置在另一幅图像上,但是事实上图像混合技术能够实现比透明度更好的混合效果;
在混合图像时,将叠放在一起的两幅图像进行逐像素颜色比较,放置于底层的图像像素颜色称为基色,放置于上层的图像像素颜色称为混合色,将这两种像素颜色按一定的计算公式计算后得到的像素颜色称为结果色,最后对合成后的图像像素应用结果色;
为了使用混合技术,需要使用context的属性:globalCompositeOperation;
globalCompositeOperation属性:
表示后绘制的图形怎样与先绘制的图形结合,其可能的值如下:
context.fillStyle="#ff0000";
context.fillRect(50,50,100,100);
context.globalCompositeOperation="screen";
context.fillStyle="rgba(0,0,255,1)";
context.fillRect(50,100,100,100);
context.globalCompositeOperation="darken";
var image=new Image();
image.src="images/s.png";
image.onload=function(){
context.drawImage(image,0,0);
var image2=new Image();
image2.src="images/2.png";
image2.onload=function(){
context.drawImage(image2,0,0);
}
}
有的时候,并不希望进行合成;比如:已经使用半透明像素在画布中绘制了内容,这个时候,想要进行临时切换,然后再恢复到原先的状态;这个时候最简单的方法就是:将使用drawImage()方法将画布内容(或一部分内容)复制到一张屏幕外画布中;但是,保存的像素都是半透明的,这个时候合成是开启的,它们并不会完全抹除临时绘制的内容;因此,在这种情况下,就需要一种方式将合成关闭:不论像素是否透明,都会绘制源像素并忽略目标像素;
组合图像:
在绘制多个图形的时候,会出现重叠的现象;此时可以将多块图形进行组合,也是使用globalCompositeOperation属性去指定组合方式,其可能的值如下:
context.fillStyle="#F00";
context.fillRect(50,50,200,200);
// context.globalCompositeOperation="source-over";
// context.globalCompositeOperation="source-atop";
// context.globalCompositeOperation="source-in";
// context.globalCompositeOperation="source-out";
// context.globalCompositeOperation="destination-over";
// context.globalCompositeOperation="destination-atop";
// context.globalCompositeOperation="destination-out";
// context.globalCompositeOperation="copy";
// context.globalCompositeOperation="lighter";
context.globalCompositeOperation="xor";
// context.fillStyle="#0F0"; // 硬透明度
var g=context.createRadialGradient(200,200,20, 200,200,120);// 软透明度
g.addColorStop(0.0, "#0F0");
g.addColorStop(1.0, "#00F");
context.fillStyle=g;
context.arc(200,200,120,0,Math.PI*2);
context.fill();
观察不同值的状态:
<input id="changeBtn" type="button" value="下一个" >
<canvas id="canvas" width="1000" height="600"></canvas>
<script>
var canvas=document.getElementsByTagName("canvas")[0];
var arr=new Array("source-over","source-in","source-out","source-atop","destination-over","destination-in","destination-out","destination-atop","lighter","xor","copy");
var i=0;
drawComposite(i);
var changeBtn=document.getElementById("changeBtn");
changeBtn.addEventListener("click", function(){
if(i++==arr.length - 1)
i=0;
drawComposite(i);
})
function drawComposite(i){
var context=canvas.getContext("2d");
context.save();
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle="blue";
context.fillRect(10,10,100,100);
context.globalCompositeOperation=arr[i];
context.beginPath();
context.fillStyle="red";
context.arc(100,100,60,0,Math.PI*2);
context.closePath();
context.fill();
context.restore();
context.font="24px 微软雅黑";
context.fillText(i + ": " + arr[i], 0, 200);
}
</script>
组合合成示例:
<script>
// 定义了一些全局变量
var canvas1=document.createElement("canvas");
var canvas2=document.createElement("canvas");
var gco=[ 'source-over','source-in','source-out','source-atop',
'destination-over','destination-in','destination-out','destination-atop',
'lighter', 'copy','xor',
'multiply', 'screen', 'overlay', 'darken',
'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light',
'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'
].reverse();
var gcoText=[
'这是默认设置,并在现有画布上下文之上绘制新图形。',
'新图形只在新图形和目标画布重叠的地方绘制。其他的都是透明的。',
'在不与现有画布内容重叠的地方绘制新图形。',
'新图形只在与现有画布内容重叠的地方绘制。',
'在现有的画布内容后面绘制新的图形。',
'现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。',
'现有内容保持在新图形不重叠的地方。',
'现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。',
'两个重叠图形的颜色是通过颜色值相加来确定的。',
'只显示新图形。',
'图像中,那些重叠和正常绘制之外的其他地方是透明的。',
'将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。',
'像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。',
'multiply 和 screen 的结合,原本暗的地方更暗,原本亮的地方更亮。',
'保留两个图层中最暗的像素。',
'保留两个图层中最亮的像素。',
'将底层除以顶层的反置。',
'将反置的底层除以顶层,然后将结果反过来。',
'屏幕相乘(A combination of multiply and screen)类似于叠加,但上下图层互换了。',
'用顶层减去底层或者相反来得到一个正值。',
'一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白。',
'和 difference 相似,但对比度较低。',
'保留了底层的亮度(luma)和色度(chroma),同时采用了顶层的色调(hue)。',
'保留底层的亮度(luma)和色调(hue),同时采用顶层的色度(chroma)。',
'保留了底层的亮度(luma),同时采用了顶层的色调 (hue) 和色度 (chroma)。',
'保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma)。'
].reverse();
var width=320;
var height=340;
window.onload=function() {
var lum={
r: 0.33,
g: 0.33,
b: 0.33
};
canvas1.width=width;
canvas1.height=height;
canvas2.width=width;
canvas2.height=height;
colorSphere();
lightMix()
runComposite();
return;
};
var colorSphere=function(element) {
var ctx=canvas1.getContext("2d");
var width=360;
var halfWidth=width / 2;
var rotate=(1 / 360) * Math.PI * 2; // per degree
var offset=0; // scrollbar offset
var oleft=-20;
var otop=-20;
for (var n=0; n <=359; n ++) {
var gradient=ctx.createLinearGradient(oleft + halfWidth, otop, oleft + halfWidth, otop + halfWidth);
var color=Color.HSV_RGB({ H: (n + 300) % 360, S: 100, V: 100 });
gradient.addColorStop(0, "rgba(0,0,0,0)");
gradient.addColorStop(0.7, "rgba("+color.R+","+color.G+","+color.B+",1)");
gradient.addColorStop(1, "rgba(255,255,255,1)");
ctx.beginPath();
ctx.moveTo(oleft + halfWidth, otop);
ctx.lineTo(oleft + halfWidth, otop + halfWidth);
ctx.lineTo(oleft + halfWidth + 6, otop);
ctx.fillStyle=gradient;
ctx.fill();
ctx.translate(oleft + halfWidth, otop + halfWidth);
ctx.rotate(rotate);
ctx.translate(-(oleft + halfWidth), -(otop + halfWidth));
}
ctx.beginPath();
ctx.fillStyle="#00f";
ctx.fillRect(15,15,30,30)
ctx.fill();
return ctx.canvas;
};
var lightMix=function() {
var ctx=canvas2.getContext("2d");
ctx.save();
ctx.globalCompositeOperation="lighter";
ctx.beginPath();
ctx.fillStyle="rgba(255,0,0,1)";
ctx.arc(100, 200, 100, Math.PI*2, 0, false);
ctx.fill()
ctx.beginPath();
ctx.fillStyle="rgba(0,0,255,1)";
ctx.arc(220, 200, 100, Math.PI*2, 0, false);
ctx.fill()
ctx.beginPath();
ctx.fillStyle="rgba(0,255,0,1)";
ctx.arc(160, 100, 100, Math.PI*2, 0, false);
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.fillStyle="#f00";
ctx.fillRect(0,0,30,30)
ctx.fill();
};
function createCanvas() {
var canvas=document.createElement("canvas");
canvas.style.background="url("+op_8x8.data+")";
canvas.style.border="1px solid #000";
canvas.style.margin="5px";
canvas.width=width/2;
canvas.height=height/2;
return canvas;
}
function runComposite() {
var dl=document.createElement("dl");
document.body.appendChild(dl);
while(gco.length) {
var pop=gco.pop();
var dt=document.createElement("dt");
dt.textContent=pop;
dl.appendChild(dt);
var dd=document.createElement("dd");
var p=document.createElement("p");
p.textContent=gcoText.pop();
dd.appendChild(p);
var canvasToDrawOn=createCanvas();
var canvasToDrawFrom=createCanvas();
var canvasToDrawResult=createCanvas();
var ctx=canvasToDrawOn.getContext('2d');
ctx.clearRect(0, 0, width, height)
ctx.save();
ctx.drawImage(canvas1, 0, 0, width/2, height/2);
ctx.fillStyle="rgba(0,0,0,0.8)";
ctx.fillRect(0, height/2 - 20, width/2, 20);
ctx.fillStyle="#FFF";
ctx.font="14px arial"; // [?ɡ?z?st??] 现存的,存在的
ctx.fillText('existing content', 5, height/2 - 5);
ctx.restore();
var ctx=canvasToDrawFrom.getContext('2d');
ctx.clearRect(0, 0, width, height)
ctx.save();
ctx.drawImage(canvas2, 0, 0, width/2, height/2);
ctx.fillStyle="rgba(0,0,0,0.8)";
ctx.fillRect(0, height/2 - 20, width/2, 20);
ctx.fillStyle="#FFF";
ctx.font="14px arial";
ctx.fillText('new content', 5, height/2 - 5);
ctx.restore();
var ctx=canvasToDrawResult.getContext('2d');
ctx.clearRect(0, 0, width, height)
ctx.save();
ctx.drawImage(canvas1, 0, 0, width/2, height/2);
ctx.globalCompositeOperation=pop;
ctx.drawImage(canvas2, 0, 0, width/2, height/2);
ctx.globalCompositeOperation="source-over";
ctx.fillStyle="rgba(0,0,0,0.8)";
ctx.fillRect(0, height/2 - 20, width/2, 20);
ctx.fillStyle="#FFF";
ctx.font="14px arial";
ctx.fillText(pop, 5, height/2 - 5);
ctx.restore();
dd.appendChild(canvasToDrawOn);
dd.appendChild(canvasToDrawFrom);
dd.appendChild(canvasToDrawResult);
dl.appendChild(dd);
}
};
// HSV (1978)=H: Hue / S: Saturation / V: Value
Color={};
Color.HSV_RGB=function (o) {
var H=o.H / 360,
S=o.S / 100,
V=o.V / 100,
R, G, B;
var A, B, C, D;
if (S==0) {
R=G=B=Math.round(V * 255);
} else {
if (H >=1) H=0;
H=6 * H;
D=H - Math.floor(H);
A=Math.round(255 * V * (1 - S));
B=Math.round(255 * V * (1 - (S * D)));
C=Math.round(255 * V * (1 - (S * (1 - D))));
V=Math.round(255 * V);
switch (Math.floor(H)) {
case 0:
R=V;
G=C;
B=A;
break;
case 1:
R=B;
G=V;
B=A;
break;
case 2:
R=A;
G=V;
B=C;
break;
case 3:
R=A;
G=B;
B=V;
break;
case 4:
R=C;
G=A;
B=V;
break;
case 5:
R=V;
G=A;
B=B;
break;
}
}
return {
R: R,
G: G,
B: B
};
};
var createInterlace=function (size, color1, color2) {
var proto=document.createElement("canvas").getContext("2d");
proto.canvas.width=size * 2;
proto.canvas.height=size * 2;
proto.fillStyle=color1; // top-left
proto.fillRect(0, 0, size, size);
proto.fillStyle=color2; // top-right
proto.fillRect(size, 0, size, size);
proto.fillStyle=color2; // bottom-left
proto.fillRect(0, size, size, size);
proto.fillStyle=color1; // bottom-right
proto.fillRect(size, size, size, size);
var pattern=proto.createPattern(proto.canvas, "repeat");
pattern.data=proto.canvas.toDataURL();
return pattern;
};
var op_8x8=createInterlace(8, "#FFF", "#eee");
</script>
像素处理;
使用canvas API能够获取图像中的每个像素,并且能够得到该像素的颜色的RGB值或RGBA值;具体就是使用context的getImageData(x, y, width, height)方法来获取原始图像数据,参数x、y表示所获取区域的起始坐标,width、height表示所获取区域的宽和高;
var image=document.getElementsByTagName("img")[0];
context.drawImage(image,0,0);
var imagedata=context.getImageData(0, 0, image.width, image.height);
console.log(imagedata); // ImageData
该方法返回一个ImageData类型的对象,表示画布矩形区域中的原始像素信息,通过该对象可以操纵像素数据、直接读取或将数据数组写入该对象中,其具有width、height、colorSpace、data等属性;其中,data属性是一个保存像素的Uint8ClampedArray类型化数组视图,内容类似[r1,g1,b1,a1,r2,g2,b2,a2,..],其中每4个元素表示一个像素信息,即为R、G、B和A分量,也就是r1、g1、b1、a1为一个像素的红、绿、蓝与透明度的值;data.length为所取得像素的数量的4倍;
某个像素的索引位置(n为第n个像素)为:(n-1)*4+0(R)、(n-1)*4+1(G)、(n-1)*4+2(B)、(n-1)*4+3(A),分别为第n个像素的 R/G/B/A 分量值;
// 取得第一个像素的值
var red=imagedata.data[0],
green=imagedata.data[1],
blue=imagedata.data[2],
alpha=imagedata.data[3];
var color=[red, green, blue, alpha];
console.log(color);
var red=imagedata.data[4],
green=imagedata.data[5],
blue=imagedata.data[6],
alpha=imagedata.data[7];
var color=[red, green, blue, alpha];
console.log(color); // [57, 133, 254, 255]
var n=9;
var red=imagedata.data[(n-1)*4+0],
green=imagedata.data[(n-1)*4+1],
blue=imagedata.data[(n-1)*4+2],
alpha=imagedata.data[(n-1)*4+3];
var color=[red, green, blue, alpha];
console.log(color);
按行和列获取某个像素RGBA分量值:row * imageData.width * 4 + col * 4 + 0|1|2|3或(row * imageData.width + col) * 4 + 0|1|2|3;
// 读取图片中位于索引为50行、索引为200列的像素的蓝色
var bluePixel=imagedata.data[50 * imagedata.width * 4 + 200 * 4 + 2];
console.log(bluePixel); // 254
// 封装一个函数
function getPixel(row, col){
var pixels=[];
for(var i=0; i<4; i++){
pixels.push(imagedata.data[(row * imagedata.width + 200) * 4 + i]);
}
return pixels;
}
var pixels=getPixel(50, 200);
console.log(pixels);
任何在画布以外的元素都会被返回成一个透明黑的 ImageData 对像;
var imagedata=context.getImageData(image.width, 0, image.width, image.height);
console.log(imagedata);
通过类型为Uint8ClampedArray的data属性,不仅能直接访问到原始图像像素数据,还能够以各种方式来操作这些数据,例如,可以修改图像像素数据,创建一个简单的灰阶过滤器,如:
var red, green, blue, alpha;
var average;
var data=imagedata.data;
for(var i=0, len=data.length; i<len; i+=4){
red=data[i];
green=data[i+1];
blue=data[i+2];
alpha=data[i+3];
average=Math.floor((red + green + blue) / 3);
data[i]=data[i+1]=data[i+2]=average;
}
imagedata.data=data;
context.putImageData(imagedata, 0, 0);
用getImageData方法获取图片信息时,该图像不能跨域;
img元素中的crossorigin属性,该属性是HTML5新增的属性,以脚本中,也有同名的属性,其决定了图片获取过程中是否开启CORS功能;有两个可能值:
默认情况下,如果没有使用这个属性,说明没有开启CORS,且值为null;
示例:颜色选择器
<style>
.container{
display: flex;
}
.container div{width:200px; border:1px solid;}
</style>
<div class="container">
<canvas id="drawing" width="300" height="200"></canvas>
<div id="hovered-color"></div>
<div id="selected-color"></div>
</div>
<script>
window.onload=function(){
var img=new Image();
img.crossOrigin='anonymous';
img.src='images/1.jpg';
var canvas=document.getElementById('drawing');
var context=canvas.getContext('2d');
img.onload=function() {
context.drawImage(img, 0, 0);
img.style.display='none';
};
var hoveredColor=document.getElementById('hovered-color');
var selectedColor=document.getElementById('selected-color');
canvas.addEventListener('mousemove', function(event) {
pick(event, hoveredColor);
});
canvas.addEventListener('click', function(event) {
pick(event, selectedColor);
});
function pick(event, destination) {
var x=event.layerX;
var y=event.layerY;
var pixel=context.getImageData(x, y, 1, 1);
var data=pixel.data;
var r=data[0], g=data[1], b=data[2], a=data[3] / 255;
var rgba="rgba(" + r + ", "+ g +", "+ b +", " + a + ")";
destination.style.background=rgba;
destination.textContent=rgba;
return rgba;
}
}
</script>
取得像素后,就可以对这些像素进行处理,如蒙版处理、面部识别等较复杂的图像处理操作;
使用putImageData(imagedata, dx, dy[, dirtyX, dirtyY, dirtyWidth, dirtyHeight])方法可以将一个已有的ImageData对象绘制到画布上,参数dx和dy表示重绘图像的起点坐标,dirtyX、dirtyY、dirtyWidth、dirtyHeight为可选,它们给出一个矩形的起点坐标及宽高,如果使用这4个参数,则只绘制像素数组中在这个矩形范围内的图像;
context.drawImage(image, 0, 0);
var imagedata=context.getImageData(0,0,image.width,image.height);
context.clearRect(0, 0, 800, 600);
context.putImageData(imagedata, 0, 0, 200, 200, 200, 100);
也可以写入到另外一个canvas中,如:
// ...
var canvas1=document.getElementById("canvas1");
var ctx=canvas1.getContext("2d");
ctx.putImageData(imagedata, 0, 0, 200, 200, 200, 100);
context.drawImage(image, 0, 0);
var imagedata=context.getImageData(0,0,image.width,image.height);
for(var i=0, len=imagedata.data.length; i<len; i++){
imagedata.data[i+0]=255 - imagedata.data[i+0]; // red
imagedata.data[i+1]=255 - imagedata.data[i+2]; // green
imagedata.data[i+2]=255 - imagedata.data[i+1]; // blue
}
context.putImageData(imagedata, 0, 0);
putImageData()会按照默认的坐标系来处理,不受画布变换矩阵的影响,而且,会忽略所有的图形属性;不会进行任何合成操作,也不会用globalAlpha乘以像素来显示,更不会绘制阴影;
putImageData()方法的原理:
function putImageData(context, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
var data=imageData.data;
var width=imageData.width;
var height=imageData.height;
dirtyX=dirtyX || 0;
dirtyY=dirtyY || 0;
dirtyWidth=dirtyWidth !==undefined ? dirtyWidth : width;
dirtyHeight=dirtyHeight !==undefined ? dirtyHeight : height;
var limitBottom=dirtyY + dirtyHeight;
var limitRight=dirtyX + dirtyWidth;
for (var y=dirtyY; y < limitBottom; y++) {
for (var x=dirtyX; x < limitRight; x++) {
var pos=y * width + x;
context.fillStyle='rgba(' + data[pos*4+0]
+ ',' + data[pos*4+1]
+ ',' + data[pos*4+2]
+ ',' + (data[pos*4+3]/255) + ')';
context.fillRect(x + dx, y + dy, 1, 1);
}
}
}
context.fillRect(0,0,100,100);
var imagedata=context.getImageData(0,0,100,100);
putImageData(context, imagedata, 150, 0, 50, 50, 25, 25);
示例:图片灰度和反相颜色
<canvas id="canvas" width="300" height="200"></canvas>
<p>
<input type="radio" id="original" name="color" value="original" checked>
<label for="original">Original</label>
<input type="radio" id="grayscale" name="color" value="grayscale">
<label for="grayscale">Grayscale</label>
<input type="radio" id="inverted" name="color" value="inverted">
<label for="inverted">Inverted</label>
</p>
<script>
var img=new Image();
img.crossOrigin='anonymous';
img.src='images/2.jpg';
var canvas=document.getElementById('canvas');
var context=canvas.getContext('2d');
img.onload=function() {
context.drawImage(img, 0, 0);
};
var original=function() {
context.drawImage(img, 0, 0);
};
var grayscale=function() {
context.drawImage(img, 0, 0);
var imageData=context.getImageData(0, 0, canvas.width, canvas.height);
var data=imageData.data;
for (var i=0; i < data.length; i +=4) {
var avg=(data[i] + data[i + 1] + data[i + 2]) / 3;
data[i]=avg; // red
data[i + 1]=avg; // green
data[i + 2]=avg; // blue
}
context.putImageData(imageData, 0, 0);
};
var invert=function() {
context.drawImage(img, 0, 0);
var imageData=context.getImageData(0, 0, canvas.width, canvas.height);
var data=imageData.data;
for (var i=0; i < data.length; i +=4) {
data[i]=255 - data[i]; // red
data[i + 1]=255 - data[i + 1]; // green
data[i + 2]=255 - data[i + 2]; // blue
}
context.putImageData(imageData, 0, 0);
};
var inputs=document.querySelectorAll('[name=color]');
for (var input of inputs) {
input.addEventListener("change", function(evt) {
switch (evt.target.value) {
case "inverted":
return invert();
case "grayscale":
return grayscale();
default:
return original();
}
});
}
</script>
createImageData(width, height | imagedata)方法:
可以创建一个空的ImageData对象,该对象中的像素是可写的,因此,可以对它们进行设置;
参数width和height为新对象的宽和高;imagedata为一个已有的ImageData对象,即复制一个和它具有相同的高和宽的对象,而图像自身不被复制;
context.rect(10, 10, 100, 100);
context.fill();
var imgdata=context.createImageData(100, 100);
console.log(imgdata); // ImageData
console.log(context.createImageData(imgdata)); // ImageData
默认情况下,这个空的ImageData对象的像素全部被预设为透明黑;如果width和height指定为负值,会被处理成相应的正值:
有了ImageData对象后,再通过putImageData()方法将这些像素复制回画布中;如:
var imagedata=context.getImageData(0, 0, canvas.width, canvas.height);
var data=context.createImageData(imagedata);
console.log(imagedata);
for(var i=0; i<imagedata.data.length; i++){
data.data[i]=imagedata.data[i];
}
console.log(data);
var canvas1=document.getElementById("canvas1");
var ctx=canvas1.getContext("2d");
ctx.putImageData(data, 0, 0);
示例:在一个画布中的图形要创建一种简单的动态模糊或“涂抺”效果;
function smear(c, n, x, y, w, h){
var pixels=c.getImageData(x,y,w,h);
var width=pixels.width, height=pixels.height;
var data=pixels.data;
var m=n - 1;
for(var row=0; row<height; row++){
var i=row * width * 4 + 4;
for(var col=1; col<width; col++, i+=4){
data[i]=(data[i] + data[i-4]*m) / n;
data[i+1]=(data[i+1] + data[i-3]*m) / n;
data[i+2]=(data[i+2] + data[i-2]*m) / n;
data[i+3]=(data[i]+3 + data[i-1]*m) / n;
}
}
c.putImageData(pixels, x, y);
}
var image=new Image();
image.src="images/1.jpg";
image.onload=function(){
context.drawImage(image,50,50);
smear(context, 50, 100, 100, 100, 100);
};
缩放和反锯齿:
过度缩放图像可能会导致图像模糊或像素化;可以通过使用2D上下文的imageSmoothingEnabled属性来控制是否在缩放图像时使用平滑算法;默认值为true,即启用平滑缩放,也可以禁用此功能,如:
context.imageSmoothingEnabled=false;
context.mozImageSmoothingEnabled=false;
context.webkitImageSmoothingEnabled=false;
context.msImageSmoothingEnabled=false;
context.imageSmoothingEnabled=false;
示例:zoom
<canvas id="canvas" width="300" height="227"></canvas>
<canvas id="zoom" width="300" height="227"></canvas>
<div>
<label for="smoothbtn">
<input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn">
Enable image smoothing
</label>
</div>
<script>
window.onload=function(){
var img=new Image();
img.src='images/3.jpg';
img.onload=function() {
draw(this);
};
function draw(img) {
var canvas=document.getElementById('canvas');
var context=canvas.getContext('2d');
context.drawImage(img, 0, 0);
img.style.display='none';
var zoomctx=document.getElementById('zoom').getContext('2d');
var smoothbtn=document.getElementById('smoothbtn');
var toggleSmoothing=function(event) {
zoomctx.imageSmoothingEnabled=this.checked;
zoomctx.mozImageSmoothingEnabled=this.checked;
zoomctx.webkitImageSmoothingEnabled=this.checked;
zoomctx.msImageSmoothingEnabled=this.checked;
};
smoothbtn.addEventListener('change', toggleSmoothing);
var zoom=function(event) {
var x=event.layerX;
var y=event.layerY;
zoomctx.drawImage(canvas,
Math.abs(x - 5),
Math.abs(y - 5),
10, 10,
0, 0,
200, 200);
};
canvas.addEventListener('mousemove', zoom);
}
}
</script>
imageSmoothingQuality属性,用于设置图像平滑度的属性,一般配合imageSmoothingEnabled属性使用;其可能的值为:"low","medium","high";
context.imageSmoothingQuality="Medium";
命中检测:
在2D绘图上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制,由于路径的使用很频繁,所以就有了一个名为isPointInPath(x, y[, fillRule])的方法,该方法接收x和y坐标作为参数,用于确定画布上的某一点是否位于当前路径上(内),该坐标是在默认坐标系中而不是在变换过的坐标系中;如:
context.rect(100, 100, 200, 100);
context.stroke();
if(context.isPointInPath(100, 100)){
alert("点(100,100)位于路径内");
}
可选的参数fillRule,用来决定点在路径内还是在路径外的算法,允许的值:"nonzero": 非零环绕规则 ,默认的规则;"evenodd": 奇偶环绕原则;
isPointInPath()还有另外一种形式:isPointInPath(path, x, y[, fillRule]);参数path为一个Path2D路径对象;
该方法用于命中检测(hit detection):检测鼠标单击事件是否发生在特定的形状上;但是,不是将MouseEvent对象的clientX和clientY属性直接传递给isPointInPath()方法;首先,必须要将鼠标事件的坐标转换成相应的画布坐标;其次,如果画布在屏幕上显示的尺寸和实际尺寸不同,鼠标事件坐标必须要进行适当的缩放,如
function hitpath(context, event){
var canvas=context.canvas;
var rect=canvas.getBoundingClientRect();
var x=(event.clientX - rect.left) * (canvas.width / rect.width);
var y=(event.clientY - rect.top) * (canvas.height / rect.height);
return context.isPointInPath(x, y);
}
canvas.onclick=function(event){
if(hitpath(this.getContext("2d"),event)){
alert("Hit");
}
}
除了进行基于路径的命中检测之外,还可以使用getImageData()方法来检测鼠标点下的像素是否已经绘制过了;如果返回的像素(单个或多个)是完全透明的,则表示该像素上没有绘制任何内容,或者认为鼠标点空了,如:
function hitpaint(context, event){
var canvas=context.canvas;
var rect=canvas.getBoundingClientRect();
var x=(event.clientX - rect.left) * (canvas.width / rect.width);
var y=(event.clientY - rect.top) * (canvas.height / rect.height);
var pixels=context.getImageData(x, y, 1, 1);
for(var i=3; i<pixels.data.length; i+=4){
if(pixels.data[i] !==0)
return true;
}
return false;
}
isPointInStroke([path,] x, y)方法:
用于检测某点是否在路径的描边线上;参数:x、y为检测点的 X 坐标Y 坐标,path为Path2D 路径;当这个点在路径的描边线上,则返回 true,否则返回 false;
context.rect(10, 10, 100, 100);
context.lineWidth=10;
context.stroke();
console.log(context.isPointInStroke(10, 10)); // true
console.log(context.isPointInStroke(12, 12)); // true
保存文件;
在画布中绘制完成一幅图形或图像后,可以将该图像或图形保存到文件中;例如,用户直接可以在画布上右击,把canvas绘图保存为一个图像文件,默认为PNG格式;
另外,也可以转换为data URL,此时是把当前的绘画状态输出到一个data URL地址所指向的数据中;
toDataURL(type [, quality])方法:可以把canvas上的绘制的图像导出,参数type,为输出数据的MIME类型;参数quality为图像质量,值为从0到1,1 表示最好品质,0 基本不被辨析,但文件更小;
var imgURI=drawing.toDataURL("image/png");
var image=document.createElement("img");
image.src=imgURI;
document.body.appendChild(image);
默认情况下,浏览器会将图像编码为PNG格式,且分辨率为96dpi;
转换为Blob对象:
也可以使用canvas对象的canvas.toBlob(callback, [mimeType[, qualityArgument]]);方法将canvas元素中的图像直接转换为一个Blob对象,参数mimeType指定图像的MIME类型,当它参数值为“image/jpeg”或“image/webp”时,可以使用qualityArgument参数指定图像质量,参数值为一个允许小数的数值,值范围为0到1之间;当转换成功时,会执行回调函数,其参数即为转换成功的Blob对象,如:
<canvas id="canvas" width="400" height="300"></canvas><br/>
<input type="button" id="btnSave" value="保存图像">
<script>
window.onload=function(){
draw('canvas');
var btnSave=document.getElementById("btnSave");
btnSave.addEventListener("click", savePic)
};
function draw(id){
var img=new Image();
img.src="images/2.jpg";
img.onload=function(){
var canvas=document.getElementById(id);
if (canvas==null)
return false;
var context=canvas.getContext("2d");
context.drawImage(img, 0, 0, canvas.width, canvas.height);
}
}
function savePic(){
canvas.toBlob(function(blob){
var a=document.createElement("a");
a.textContent="打开图像";
document.body.appendChild(a);
a.style.display="block";
a.href=URL.createObjectURL(blob);
},"image/png",0.95);
}
</script>
canvas元素的toBlob方法是非常重要的,因为如果使用Blob对象,可以使用Blob对象的size属性获取输出后的文件尺寸;在将canvas元素中的图像进行输出或将其提交到服务器端时,如果使用Data URL,由于图像数据为文本数据,对于大数据图像,将大幅度增加浏览器端的负担,如果使用Blob对象,由于浏览器内部使用二进制数据,会大幅度减轻浏览器的负担;
解码图像:
针对一个cavnas元素来说,无论是修改、剪切或缩放其中的图像后再利用时,首先要做的一件事情是解码其中的图像;问题在于当对canvas元素中的图像进行解码时,可能需要耗费较大的CPU资源;
使用window对象的createImageBitmap方法,可用于后台解码图像,其返回一个ImageBitmap对象,开发者可以将该对象中存储的图像绘制到一个canvas元素中;
createImageBitmap(image[, sx, sy, sw, sh]).then(function(response) {…}); 参数image用于指定图像来源,其可以为一个img元素、video元素、canvas、Blob、ImageData、ImageBitmap等;参数sx, sy, sw, sh分别用于指定被复制区域的起始坐标及宽和高;
该方法返回一个以一个ImageBitmap对象为结果的Promise对象,该对象中包含了指定区域的图像;
var image=document.getElementsByTagName("img")[0];
image.onload=function(){
// var imageBitmap=window.createImageBitmap(image);
var imageBitmap=window.createImageBitmap(image, 50, 50, 200, 100);
console.log(imageBitmap); // Promise
}
例如,绘制一个图像到画布中
function draw(id){
fetch("images/1.jpg")
.then(response=> response.blob())
.catch(error=> console.error("Error:",error))
.then(response=> {
let canvas=document.getElementById(id);
let context=canvas.getContext("2d");
createImageBitmap(response,50,50,400,300).then(
imageBitmap=> context.drawImage(imageBitmap,0,0));
});
}
draw("canvas");
ImageBitmap对象:
ImageBitmap 接口表示能够被绘制到 <canvas> 上的位图图像,具有低延迟的特性;一般由createImageBitmap()方法返回,并且它可以从多种源中生成,如img、canvas、video等;
ImageBitmap提供了一种异步且高资源利用率的方式来为WebGL的渲染准备基础结构;
属性:
width:只读,无符号长整型数值,表示ImageData对象的宽度,单位为像素;
height:只读,无符号长整型数值,表示ImageData对象的高度;
方法:
close():释放ImageBitmap所相关联的所有图形资源;
可以在Web Worker中使用createImageBitmap方法;如果有许多图像需要解码,可以将URL传递给Web Worker,在其中下载并解码图像,然后将解码结果传递给主线程以便将其绘制到canvas中;
worker.js代码:
onmessage=function(event){
fetch(event.data).then(response=> response.blob())
.catch(error=> self.postMessage(error))
.then(response=> {
createImageBitmap(response,23,5,57,80)
.then(imageBitmap=> self.postMessage({imageBitmap:imageBitmap});)
});
}
js代码:
function draw(id){
let canvas=document.getElementById(id);
let context=canvas.getContext("2d");
let worker=new Worker("worker.js");
worker.postMessage("images/1.jpg");
worker.onmessage=(evt)=> {
if(evt.data.err)
console.log(evt.data.message);
context.drawImage(evt.data.imageBitmap,0,0);
};
}
createImageBitmap方法还有一个可选的options参数,其为一个设置选项的对象;可用的选项为:
// ...
createImageBitmap(response,50,50,400,300, {
imageOrientation:"flipY",
premultiplyAlpha:"premultiply",
colorSpaceConversion:"none",
resizeWidth:200,
resizeHeight:150,
resizeQuality:"medium"
}).then(
imageBitmap=> context.drawImage(imageBitmap,0,0));
动画的制作:
在Canvas画布中制作动画相对来说比较简单,实际上就是一个不断擦除、重绘、擦除、重绘的过程;
基本步骤:
操控动画:
在绘图图形图像时,仅仅在脚本执行结束后才能看见结果,所以,在类似for循环体里实现动画是不太可能的;
因此,为了实现动画,需要一些可以定时执行重绘的方法;即可以通过 setInterval、setTimeout 和window.requestAnimationFrame()方法来控制在设定的时间点上执行重绘,从而操控动画;
如果并不需要与用户互动,可以使用 setInterval() 方法;如果需要做一个游戏,可以使用键盘或者鼠标事件配合上setTimeout()方法来实现,通过设置事件监听,可以捕捉用户的交互,并执行相应的动作;
如:一个走动的小方块
var context;
var w,h,i;
var timer=null;
function draw(id){
var canvas=document.getElementById(id);
if(canvas==null){
return false;
}
context=canvas.getContext("2d");
context.fillStyle="#EEE";
context.fillRect(0,0,400,300);
w=canvas.width;
h=canvas.height;
i=0;
timer=setInterval(rotate,100);
}
function rotate(){
if(i>=w -20)
clearInterval(timer);
context.clearRect(0,0,w,h);
context.fillStyle="red";
context.fillRect(i,0,20,20);
i=i + 20;
}
draw("canvas");
示例:图形组合变换
var globalId;
var i=0;
function draw(id){
globalId=id;
setInterval(Composite,1000);
}
function Composite(){
var canvas=document.getElementById(globalId);
if(canvas==null)
return false;
var context=canvas.getContext("2d");
var arr=new Array("source-atop","source_in","source-out","source-over","destination-atop","destination-in","destination-out","destination-over","lighter","copy","xor");
if(i>10) i=0;
context.clearRect(0,0,canvas.width,canvas.height);
context.save();
context.fillStyle="blue";
context.fillRect(10,10,60,60);
context.globalCompositeOperation=arr[i];
context.beginPath();
context.fillStyle="red";
context.arc(60,60,30,0,Math.PI*2,false);
context.fill();
context.restore();
i=i+1;
}
draw("canvas");
示例:太阳系的动画
<canvas id="canvas" width="600" height="600"></canvas>
<script>
var sun=new Image();
var moon=new Image();
var earth=new Image();
function init(){
sun.src='images/sun.png';
moon.src='images/moon.png';
earth.src='images/earth.png';
window.requestAnimationFrame(draw);
}
function draw() {
var ctx=document.getElementById('canvas').getContext('2d');
ctx.globalCompositeOperation='destination-over';
ctx.clearRect(0,0,300,300); // clear canvas
ctx.fillStyle='rgba(0,0,0,0.4)';
ctx.strokeStyle='rgba(0,153,255,0.4)';
ctx.save();
ctx.translate(150,150);
// Earth
var time=new Date();
ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() );
ctx.translate(105,0);
ctx.fillRect(0,-12,50,24); // Shadow
ctx.drawImage(earth,-12,-12);
// Moon
ctx.save();
ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() );
ctx.translate(0,28.5);
ctx.drawImage(moon,-3.5,-3.5);
ctx.restore();
ctx.restore();
ctx.beginPath();
ctx.arc(150,150,105,0,Math.PI*2,false); // Earth orbit
ctx.stroke();
ctx.drawImage(sun,0,0,300,300);
window.requestAnimationFrame(draw);
}
init();
</script>
示例:动画时钟
<canvas id="canvas" width="600" height="600"></canvas>
<script>
function clock(){
var context=document.getElementById('canvas').getContext('2d');
context.save();
context.clearRect(0,0,150,150);
context.translate(75,75);
context.scale(0.4,0.4);
context.rotate(-Math.PI/2);
context.strokeStyle="black";
context.lineWidth=8;
context.lineCap="round";
// 小时刻度
context.save();
for (var i=0;i<12;i++){
context.beginPath();
context.rotate(Math.PI/6);
context.moveTo(100,0);
context.lineTo(120,0);
context.stroke();
}
context.restore();
context.save();
context.lineWidth=5;
for (i=0;i<60;i++){
if (i%5 !=0) {
context.beginPath();
context.moveTo(117,0);
context.lineTo(120,0);
context.stroke();
}
context.rotate(Math.PI/30);
}
context.restore();
var now=new Date();
var sec=now.getSeconds();
var min=now.getMinutes();
var hr=now.getHours();
hr=hr>=12 ? hr-12 : hr;
context.fillStyle="black";
context.save();
context.rotate(hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec)
context.lineWidth=14;
context.beginPath();
context.moveTo(-20,0);
context.lineTo(80,0);
context.stroke();
context.restore();
context.save();
context.rotate((Math.PI/30)*min + (Math.PI/1800)*sec)
context.lineWidth=10;
context.beginPath();
context.moveTo(-28,0);
context.lineTo(112,0);
context.stroke();
context.restore();
context.save();
context.rotate(sec * Math.PI/30);
context.strokeStyle="#D40000";
context.fillStyle="#D40000";
context.lineWidth=6;
context.beginPath();
context.moveTo(-30,0);
context.lineTo(83,0);
context.stroke();
context.beginPath();
context.arc(0, 0, 10, 0, Math.PI*2, true);
context.fill();
context.beginPath();
context.arc(95, 0, 10, 0, Math.PI*2, true);
context.stroke();
context.beginPath();
context.fillStyle="rgba(0,0,0,1)";
context.arc(0,0,3,0,Math.PI*2,true);
context.fill();
context.restore();
context.beginPath();
context.lineWidth=14;
context.strokeStyle='#325FA2';
context.arc(0, 0, 142, 0, Math.PI*2, true);
context.stroke();
context.restore();
window.requestAnimationFrame(clock);
}
window.requestAnimationFrame(clock);
</script>
示例:循环全景照片
<canvas id="canvas" width="800" height="200"></canvas>
<script>
var img=new Image();
img.src='images/park.jpg';
var CanvasXSize=800, CanvasYSize=200;
var speed=30;
var scale=1.05;
var y=-4.5;
// 主程序
var dx=0.75;
var imgW, imgH;
var x=0;
var clearX, clearY;
var ctx;
img.onload=function() {
imgW=img.width * scale;
imgH=img.height * scale;
if (imgW > CanvasXSize) {
x=CanvasXSize - imgW; // -275.2
clearX=imgW;
} else {
clearX=CanvasXSize;
}
if (imgH > CanvasYSize) {
clearY=imgH;
} else {
clearY=CanvasYSize;
}
ctx=document.getElementById('canvas').getContext('2d');
setInterval(draw, speed);
}
function draw() {
ctx.clearRect(0, 0, clearX, clearY);
if (imgW <=CanvasXSize) {
if (x > CanvasXSize) {
x=-imgW + x;
}
if (x > 0) {
ctx.drawImage(img, -imgW + x, y, imgW, imgH);
}
if (x - imgW > 0) {
ctx.drawImage(img, -imgW * 2 + x, y, imgW, imgH);
}
} else {
if (x > CanvasXSize) {
x=CanvasXSize - imgW;
}
if (x > (CanvasXSize - imgW)) {
ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);
}
}
ctx.drawImage(img, x, y, imgW, imgH);
x +=dx;
}
</script>
示例:鼠标追踪动画
<style>
#cw {position: fixed; z-index: -1;}
body {margin: 0; padding: 0; background-color: rgba(0,0,0,0.05);}
</style>
<canvas id="cw"></canvas>
<script>
var cn;
//=document.getElementById('cw');
var c;
var u=10;
const m={
x: innerWidth / 2,
y: innerHeight / 2
};
window.onmousemove=function(e) {
m.x=e.clientX;
m.y=e.clientY;
}
function gc() {
var s="0123456789ABCDEF";
var c="#";
for (var i=0; i < 6; i++) {
c +=s[Math.ceil(Math.random() * 15)]
}
return c
}
var a=[];
window.onload=function myfunction() {
cn=document.getElementById('cw');
c=cn.getContext('2d');
for (var i=0; i < 10; i++) {
var r=30;
var x=Math.random() * (innerWidth - 2 * r) + r;
var y=Math.random() * (innerHeight - 2 * r) + r;
var t=new ob(innerWidth / 2,innerHeight / 2,5,"red",Math.random() * 200 + 20,2);
a.push(t);
}
//cn.style.backgroundColor="#700bc8";
c.lineWidth="2";
c.globalAlpha=0.5;
resize();
anim()
}
window.onresize=function() {
resize();
}
function resize() {
cn.height=innerHeight;
cn.width=innerWidth;
for (var i=0; i < 101; i++) {
var r=30;
var x=Math.random() * (innerWidth - 2 * r) + r;
var y=Math.random() * (innerHeight - 2 * r) + r;
a[i]=new ob(innerWidth / 2,innerHeight / 2,4,gc(),Math.random() * 200 + 20,0.02);
}
// a[0]=new ob(innerWidth / 2, innerHeight / 2, 40, "red", 0.05, 0.05);
//a[0].dr();
}
function ob(x, y, r, cc, o, s) {
this.x=x;
this.y=y;
this.r=r;
this.cc=cc;
this.theta=Math.random() * Math.PI * 2;
this.s=s;
this.o=o;
this.t=Math.random() * 150;
this.o=o;
this.dr=function() {
const ls={
x: this.x,
y: this.y
};
this.theta +=this.s;
this.x=m.x + Math.cos(this.theta) * this.t;
this.y=m.y + Math.sin(this.theta) * this.t;
c.beginPath();
c.lineWidth=this.r;
c.strokeStyle=this.cc;
c.moveTo(ls.x, ls.y);
c.lineTo(this.x, this.y);
c.stroke();
c.closePath();
}
}
function anim() {
requestAnimationFrame(anim);
c.fillStyle="rgba(0,0,0,0.05)";
c.fillRect(0, 0, cn.width, cn.height);
a.forEach(function(e, i) {
e.dr();
});
}
</script>
常规动画设计:
绘制小球:
<canvas id="canvas" width="600" height="300"></canvas>
<script>
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var ball={
x: 100,
y: 100,
radius: 25,
color: 'blue',
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle=this.color;
ctx.fill();
}
};
ball.draw();
</script>
添加速率并实现动画:
<canvas id="canvas" width="600" height="300"></canvas>
<script>
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var rafID;
var ball={
x: 100,
y: 100,
vx: 5,
vy: 2,
radius: 25,
color: 'blue',
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle=this.color;
ctx.fill();
}
};
function drawAnimation() {
ctx.clearRect(0,0, canvas.width, canvas.height);
ball.draw();
ball.x +=ball.vx;
ball.y +=ball.vy;
rafID=window.requestAnimationFrame(drawAnimation);
}
canvas.addEventListener('mouseover', function(e){
rafID=window.requestAnimationFrame(drawAnimation);
});
canvas.addEventListener('mouseout', function(e){
window.cancelAnimationFrame(rafID);
});
ball.draw();
</script>
运动边界:
// 在drawAnimation()函数中添加
if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
ball.vy=-ball.vy;
}
if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
ball.vx=-ball.vx;
}
rafID=window.requestAnimationFrame(drawAnimation);
设置加速度:
ball.vy *=.99;
ball.vy +=.25;
// 添加到drawAnimation()函数中
ball.vy *=.99;
ball.vy +=.25;
ball.x +=ball.vx;
ball.y +=ball.vy;
长尾效果:
// ctx.clearRect(0,0, canvas.width, canvas.height);
// 如:在drawAnimation中用以下代码替代掉上方的clearRect()方法
ctx.fillStyle='rgba(255,255,255,0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
ball.draw();
添加鼠标控制:
// ...
// 添加一个全局变量,用于判断是否正在运动
var running=false;
// ...
function clear() {
ctx.fillStyle='rgba(255,255,255,0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
}
canvas.addEventListener('mousemove', function(e){
if(!running){
clear();
ball.x=e.offsetX;
ball.y=e.offsetY;
ball.draw();
}
});
canvas.addEventListener('mouseout', function(e){
window.cancelAnimationFrame(rafID);
running=false;
});
canvas.addEventListener('click',function(e){
if (!running) {
rafID=window.requestAnimationFrame(drawAnimation);
running=true;
}
});
// ...
使用 canvas 处理视频:
通过canvas和video,可以实时地操纵视频数据来合成各种视觉特效,并把结果呈现到视频画面中;
例如:色度键控(也被称为“绿屏效果”)
<style>
body {background: black; color:#CCCCCC;}
#c2 {background-image: url(url("images/logo.png")); background-repeat: no-repeat;}
div {float: left; border :1px solid #444444;
padding:10px; margin: 10px; background:#3B3B3B;}
</style>
<div>
<video id="video" src="images/video.ogv" controls="true"/>
</div>
<div>
<canvas id="c1" width="160" height="96"></canvas>
<canvas id="c2" width="160" height="96"></canvas>
</div>
JavaScript:main.js
var processor={};
processor.doLoad=function doLoad() {
this.video=document.getElementById('video');
this.c1=document.getElementById('c1');
this.ctx1=this.c1.getContext('2d');
this.c2=document.getElementById('c2');
this.ctx2=this.c2.getContext('2d');
var self=this;
this.video.addEventListener('play', function() {
self.width=self.video.videoWidth / 2;
self.height=self.video.videoHeight / 2;
self.timerCallback();
}, false);
},
主页面:
<script src="video_main.js"></script>
<script>
window.onload=processor.doLoad();
</script>
实现计时器回调timerCallback()方法:
processor.timerCallback=function timerCallback() {
if (this.video.paused || this.video.ended) {
return;
}
this.computeFrame();
var self=this;
setTimeout(function() {
self.timerCallback();
}, 0);
},
实现computeFrame()方法用来操作视频帧数据:
processor.computeFrame=function computeFrame() {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
var frame=this.ctx1.getImageData(0, 0, this.width, this.height);
var l=frame.data.length / 4;
for (var i=0; i < l; i++) {
var r=frame.data[i * 4 + 0];
var g=frame.data[i * 4 + 1];
var b=frame.data[i * 4 + 2];
if (g > 100 && r > 100 && b < 43)
frame.data[i * 4 + 3]=0;
}
this.ctx2.putImageData(frame, 0, 0);
return;
}
canvas 的优化:
避免浮点数的坐标点,应该使用整数:
当在绘制一个没有整数坐标点的图形时会发生子像素渲染;
ctx.drawImage(myImage, 0.3, 0.5);
浏览器为了达到抗锯齿的效果会做额外的运算;为了避免这种情况,应该用Math.floor()函数对所有的坐标点取整;
尽量不要在用drawImage时缩放图像:
因为浏览器一样需要额外的运算,去处理缩放后的图像;
避免使用多层画布去画一个复杂的场景:
在有些应用中,可能某些图形需要经常移动或更改,而其他对象则保持相对静态;在这种情况下,可以使用多个<canvas>元素对项目进行分层;
<style>
#stage {
width: 480px; height: 320px;
position: relative; border: 2px solid black
}
canvas { position: absolute; }
#ui-layer { z-index: 3 }
#game-layer { z-index: 2 }
#background-layer { z-index: 1 }
</style>
<div id="stage">
<canvas id="ui-layer" width="480" height="320"></canvas>
<canvas id="game-layer" width="480" height="320"></canvas>
<canvas id="background-layer" width="480" height="320"></canvas>
</div>
用CSS设置大的背景图:
可以避免在每一帧在画布上绘制大图;
用CSS transforms特性缩放画布:
因为CSS transforms使用 GPU,速度很快,所以最好的情况是不直接缩放画布;
var scaleX=window.innerWidth / canvas.width;
var scaleY=window.innerHeight / canvas.height;
var scaleToFit=Math.min(scaleX, scaleY);
var scaleToCover=Math.max(scaleX, scaleY);
stage.style.transformOrigin='0 0';
stage.style.transform='scale(' + scaleToFit + ')';
关闭透明度:
如果画布不需要透明,当使用getContext()方法创建一个绘图上下文时把 alpha 选项设置为 false,这个选项可以帮助浏览器进行内部优化;
var ctx=canvas.getContext('2d', { alpha: false });
其它建议:
*请认真填写需求信息,我们会在24小时内与您取得联系。