html5橡皮檫特效
体验效果:
http://keleyi.com/keleyi/phtml/html5/32.htm
效果描述:
有点像刮刮卡一样,在移动设备上,把某张图片刮掉显示出另一张图片。效果图如下:
这种刮图的效果,首先想到就是用HTML5的canvas来实现,而canvas的API中,可以清除像素的就是clearRect方法,但是clearRect方法的清除区域矩形,毕竟大部分人的习惯中的橡皮擦都是圆形的,所以就引入了剪辑区域这个强大的功能,也就是clip方法。用法很简单:
ctx.save()
ctx.beginPath()
ctx.arc(x2,y2,a,0,2*Math.PI);
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore();
上面那段代码就实现了圆形区域的擦除,也就是先实现一个圆形路径,然后把这个路径作为剪辑区域,再清除像素就行了。有个注意点就是需要先保存绘图环境,清除完像素后要重置绘图环境,如果不重置的话以后的绘图都是会被限制在那个剪辑区域中。
擦除效果有了,现在就是写鼠标移动擦除的效果了,下面我均用鼠标来描述,因为移动端也差不多,就是把mousedown换成touchstart,mousemove换成touchmove,mouseup换成touchend、以及获取坐标点由e.clientX换成e.targetTouches[0].pageX而已。
实现鼠标移动擦除,刚开始就是想到鼠标移动时在触发的mousemove事件中对鼠标所在位置进行圆形区域擦除,写出来后发现,当鼠标移动速度很快的时候,擦除的区域就不连贯了,就会出现下面这种效果,这显然不是我们想要的橡皮擦擦除效果。

既然所有点不连贯,那接下来要做的事就是把这些点连贯起来,如果是实现画图功能的话,就可以直接通过lineTo把两点之间连接起来再绘制,但是擦除效果中的剪辑区域要求要是闭合路径,如果是单纯的把两个点连起来就无法形成剪辑区域了。然后我就想到用计算的方法,算出两个擦除区域中的矩形四个端点坐标来实现,也就是下图中的红色矩形:

计算方法也很简单,因为可以知道两个剪辑区域连线两个端点的坐标,又知道我们要多宽的线条,矩形的四个端点坐标就变得容易求了,所以就有了下面的代码:
var asin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)));
var acos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)))
var x3 = x1+asin;
var y3 = y1-acos;
var x4 = x1-asin;
var y4 = y1+acos;
var x5 = x2+asin;
var y5 = y2-acos;
var x6 = x2-asin;
var y6 = y2+acos;
x1、y1和x2、y2就是两个端点,从而求出了四个端点的坐标。这样一来,剪辑区域就是圈加矩形,代码组织起来就是:
var hastouch = "ontouchstart" in window?true:false,//判断是否为移动设备
tapstart = hastouch?"touchstart":"mousedown",
tapmove = hastouch?"touchmove":"mousemove",
tapend = hastouch?"touchend":"mouseup"; canvas.addEventListener(tapstart , function(e){
e.preventDefault(); x1 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y1 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; //鼠标第一次点下的时候擦除一个圆形区域,同时记录第一个坐标点
ctx.save()
ctx.beginPath()
ctx.arc(x1,y1,a,0,2*Math.PI);
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore(); canvas.addEventListener(tapmove , tapmoveHandler);
canvas.addEventListener(tapend , function(){
canvas.removeEventListener(tapmove , tapmoveHandler);
});
//鼠标移动时触发该事件
function tapmoveHandler(e){
e.preventDefault()
x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; //获取两个点之间的剪辑区域四个端点
var asin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)));
var acos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)))
var x3 = x1+asin;
var y3 = y1-acos;
var x4 = x1-asin;
var y4 = y1+acos;
var x5 = x2+asin;
var y5 = y2-acos;
var x6 = x2-asin;
var y6 = y2+acos; //保证线条的连贯,所以在矩形一端画圆
ctx.save()
ctx.beginPath()
ctx.arc(x2,y2,a,0,2*Math.PI);
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore(); //清除矩形剪辑区域里的像素
ctx.save()
ctx.beginPath()
ctx.moveTo(x3,y3);
ctx.lineTo(x5,y5);
ctx.lineTo(x6,y6);
ctx.lineTo(x4,y4);
ctx.closePath();
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore(); //记录最后坐标
x1 = x2;
y1 = y2;
}
})
如此一来,鼠标擦除的效果就实现了,不过还有一个要实现的点,就是大部分擦除的效果,当你擦了一定数量的像素后,就会自动把所有图片内容呈现出来,这个效果,我是用imgData来实现的。代码如下:
var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);
var dd = 0;
for(var x=0;x<imgData.width;x+=1){
for(var y=0;y<imgData.height;y+=1){
var i = (y*imgData.width + x)*4;
if(imgData.data[i+3] > 0){
dd++
}
}
}
if(dd/(imgData.width*imgData.height)<0.4){
canvas.className = "noOp";
}
获取到imgData,对imgData里的像素进行遍历,然后再对imgData的data数组里的rgba中的alpha进行分析,也就是分析透明度,如果像素被擦除了,那透明度就是0了,也就是把当前画布中透明度不为0的像素的数量跟画布总像素数进行比较,如果透明度不为0 的像素数比例低于40%,那说明当前画布上就以后有百分六十以上的区域被擦除了,就可以自动呈现图片了。
此处注意,我是把检查像素这段代码方法mouseup事件里面的,因为这个计算量相对来说还是不小,如果用户狂点鼠标,就会狂触发mouseup事件,也就是会疯狂的触发那个循环计算像素,计算量大到阻塞进程,导致界面卡住的情况,缓解办法如下:加个timeout,延迟执行像素计算,而在每一次点击的时候再清除timeout,也就是如果用户点击很快,这个计算也就触发不了了,还有一个提升的办法就是抽样检查,我上面的写法是逐个像素检查,逐个像素检查的话像素量太大,肯定会卡的,所以可以采用抽样检查,比如每隔30个像素检查一次,修改后的代码如下:
timeout = setTimeout(function(){
var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);
var dd = 0;
for(var x=0;x<imgData.width;x+=30){
for(var y=0;y<imgData.height;y+=30){
var i = (y*imgData.width + x)*4;
if(imgData.data[i+3] >0){
dd++
}
}
}
if(dd/(imgData.width*imgData.height/900)<0.4){
canvas.className = "noOp";
}
},100)
这样就可以较大限度的防止用户狂点击了,如果有其他更好的检查方法欢迎给出意见,谢谢。
到了这一步就都写完了,然后就是测试的时候了,结果并不乐观,在android上还是卡啊卡啊,所以又得另想办法,最终发现了绘图环境中的globalCompositeOperation这个属性,这个属性的默认值是source-over,也就是,当你在已有像素上进行绘图时会叠加,但是还有一个属性是destination-out,官方解释就是:在源图像外显示目标图像。只有源图像外的目标图像部分才会被显示,源图像是透明的。好像不太好理解,但是其实自己测试一下就会发现很简单,也就是在已有像素的基础上进行绘图时,你绘制的区域里的已有像素都会被置为透明,直接看张图更容易理解:

globalCompositeOperation属性效果图解。
有了这个属性后,就意味着不需要用到clip,也就不需要用sin、cos什么的计算剪辑区域,直接用条粗线就行了,这样一来就能够很大限度的降低了计算量,同时减少了绘图环境API的调用,性能提升了,在android上运行应该也会流畅很多,下面是修改后的代码:
//通过修改globalCompositeOperation来达到擦除的效果
function tapClip(){
var hastouch = "ontouchstart" in window?true:false,
tapstart = hastouch?"touchstart":"mousedown",
tapmove = hastouch?"touchmove":"mousemove",
tapend = hastouch?"touchend":"mouseup"; canvas.addEventListener(tapstart , function(e){
clearTimeout(timeout)
e.preventDefault(); x1 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y1 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; ctx.lineCap = "round"; //设置线条两端为圆弧
ctx.lineJoin = "round"; //设置线条转折为圆弧
ctx.lineWidth = a*2;
ctx.globalCompositeOperation = "destination-out"; ctx.save();
ctx.beginPath()
ctx.arc(x1,y1,1,0,2*Math.PI);
ctx.fill();
ctx.restore(); canvas.addEventListener(tapmove , tapmoveHandler);
canvas.addEventListener(tapend , function(){
canvas.removeEventListener(tapmove , tapmoveHandler); timeout = setTimeout(function(){
var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);
var dd = 0;
for(var x=0;x<imgData.width;x+=30){
for(var y=0;y<imgData.height;y+=30){
var i = (y*imgData.width + x)*4;
if(imgData.data[i+3] > 0){
dd++
}
}
}
if(dd/(imgData.width*imgData.height/900)<0.4){
canvas.className = "noOp";
}
},100)
});
function tapmoveHandler(e){
e.preventDefault()
x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; ctx.save();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.restore() x1 = x2;
y1 = y2;
}
})
}
擦除那部分代码就这么一点,也就相当于画图功能,直接设置line属性后通过lineTo进行绘制线条,只要事前把globalCompositeOperation设成destination-out,你所进行的一切绘制,都变成了擦除效果。鼠标滑动触发的事件里面代码也少了很多,绘图对象的调用次数减少了,计算也减少了,性能提升大大滴。
改好代码后就立即用自己的android机子测试了一下,果然如此,跟上一个相比,流畅了很多,至少达到了客户要求的能玩的地步了。
附完整HTML文件代码:
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0;">
<title>HTML5橡皮檫-柯乐义</title>
<style type="text/css">
body {
margin: 0;
padding: 0;
} .box {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background: url("http://keleyi.com/keleyi/phtml/html5/32/keleyi.jpg") no-repeat;
background-size: 100% 100%;
backface-visibility: hidden;
overflow: hidden;
} #cas_keleyi_com {
width: 100%;
height: 100%;
opacity: 1;
-webkit-transition: opacity .5s;
-ms-transition: opacity .5s;
-moz-transition: opacity .5s;
} .noOp {
opacity: 0 !important;
}
</style>
</head>
<body>
<div class="box" id="bb">
<canvas id="cas_keleyi_com"></canvas>
<a href="http://keleyi.com">首页</a> <a href="http://keleyi.com/a/bjae/uvolnjek.htm">原文</a>
</div> <script type="text/javascript" charset="utf-8">
var canvas = document.getElementById("cas_ke"+"leyi_com"),ctx = canvas.getContext("2d");
var x1,y1,a=30,timeout,totimes = 100,jiange = 30;
canvas.width = document.getElementById("bb").clientWidth;
canvas.height = document.getElementById("bb").clientHeight;
var img = new Image();
img.src = "http://keleyi.com/keleyi/phtml/html5/32/hovertree.jpg";
img.onload = function(){
ctx.drawImage(img,0,0,canvas.width,canvas.height)
//ctx.fillRect(0,0,canvas.width,canvas)
tapClip()
} //通过修改globalCompositeOperation来达到擦除的效果
function tapClip(){
var hastouch = "ontouchstart" in window?true:false,
tapstart = hastouch?"touchstart":"mousedown",
tapmove = hastouch?"touchmove":"mousemove",
tapend = hastouch?"touchend":"mouseup"; ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = a*2;
ctx.globalCompositeOperation = "destination-out"; canvas.addEventListener(tapstart , function(e){
clearTimeout(timeout)
e.preventDefault(); x1 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y1 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; ctx.save();
ctx.beginPath()
ctx.arc(x1,y1,1,0,2*Math.PI);
ctx.fill();
ctx.restore(); canvas.addEventListener(tapmove , tapmoveHandler);
canvas.addEventListener(tapend , function(){
canvas.removeEventListener(tapmove , tapmoveHandler); timeout = setTimeout(function(){
var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);
var dd = 0;
for(var x=0;x<imgData.width;x+=jiange){
for(var y=0;y<imgData.height;y+=jiange){
var i = (y*imgData.width + x)*4;
if(imgData.data[i+3] >0){
dd++
}
}
}
if(dd/(imgData.width*imgData.height/(jiange*jiange))<0.4){
canvas.className = "noOp";
}
},totimes)
});
function tapmoveHandler(e){
clearTimeout(timeout)
e.preventDefault()
x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; ctx.save();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.restore() x1 = x2;
y1 = y2;
}
})
} //使用clip来达到擦除效果
function otherClip(){
var hastouch = "ontouchstart" in window?true:false,
tapstart = hastouch?"touchstart":"mousedown",
tapmove = hastouch?"touchmove":"mousemove",
tapend = hastouch?"touchend":"mouseup"; canvas.addEventListener(tapstart , function(e){
clearTimeout(timeout)
e.preventDefault(); x1 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y1 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; ctx.save()
ctx.beginPath()
ctx.arc(x1,y1,a,0,2*Math.PI);
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore(); canvas.addEventListener(tapmove , tapmoveHandler);
canvas.addEventListener(tapend , function(){
canvas.removeEventListener(tapmove , tapmoveHandler); timeout = setTimeout(function(){
var imgData = ctx.getImageData(0,0,canvas.width,canvas.height);
var dd = 0;
for(var x=0;x<imgData.width;x+=jiange){
for(var y=0;y<imgData.height;y+=jiange){
var i = (y*imgData.width + x)*4;
if(imgData.data[i+3] >0){
dd++
}
}
}
if(dd/(imgData.width*imgData.height/(jiange*jiange))<0.4){
canvas.className = "noOp";
}
},totimes) }); function tapmoveHandler(e){
e.preventDefault()
x2 = hastouch?e.targetTouches[0].pageX:e.clientX-canvas.offsetLeft;
y2 = hastouch?e.targetTouches[0].pageY:e.clientY-canvas.offsetTop; var asin = a*Math.sin(Math.atan((y2-y1)/(x2-x1)));
var acos = a*Math.cos(Math.atan((y2-y1)/(x2-x1)));
var x3 = x1+asin;
var y3 = y1-acos;
var x4 = x1-asin;
var y4 = y1+acos;
var x5 = x2+asin;
var y5 = y2-acos;
var x6 = x2-asin;
var y6 = y2+acos; ctx.save()
ctx.beginPath()
ctx.arc(x2,y2,a,0,2*Math.PI);
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore(); ctx.save()
ctx.beginPath()
ctx.moveTo(x3,y3);
ctx.lineTo(x5,y5);
ctx.lineTo(x6,y6);
ctx.lineTo(x4,y4);
ctx.closePath();
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore(); x1 = x2;
y1 = y2;
}
})
}
</script>
</body>
</html>
本文转载:http://keleyi.com/a/bjae/uvolnjek.htm
html5橡皮檫特效的更多相关文章
- HTML5火焰文字特效DEMO演示
效果展示:http://hovertree.com/texiao/html5/26/ 效果图: 扫描二维码查看效果:
- HTML5火焰文字特效DEMO演示---转载
只有google支持 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...
- 纯JavaScript实现HTML5 Canvas六种特效滤镜
纯JavaScript实现HTML5 Canvas六种特效滤镜 小试牛刀,实现了六款简单常见HTML5 Canvas特效滤镜,并且封装成一个纯 JavaScript可调用的API文件gloomyfi ...
- 16个富有创意的HTML5 Canvas动画特效集合
HTML5技术正在不断的发展和更新,越来越多的开发者也正在加入HTML5阵营,甚至在移动开发上HTML5的地位也是越来越重要了.HTML5中的大部分动画都是通过Canvas实现,因为Canvas就像一 ...
- 7款让人惊叹的HTML5粒子动画特效(转载)
1.HTML5 Canvas粒子模拟效果 这是一款利用HTML5 Canvas模拟出来的30000个粒子动画,当你用鼠标在canvas画布上移动时,鼠标周围的一些粒子就会跟着你移动,并形成一定的图案, ...
- 7款让人惊叹的HTML5粒子动画特效
HTML5的很大一个优势就是可以更加便捷高效地制作网页粒子动画特效,特别是Canvas特性,可以实现在网页上绘制任何图形和动画.本文要分享7款让人惊叹的HTML5粒子动画特效,这些粒子特效都提供源代码 ...
- 非常酷的jQuery/HTML5图片滑块特效 带弹性菜单
新的一周刚刚开始,当我迷迷糊糊坐在办公桌前时,又不自主的去看了一些jQuery和HTML5的应用插件,今天我们来看一款非常酷的jQuery/HTML5图片滑块特效,这款插件的特点是图片上不错的弹性菜单 ...
- 分享8款令人惊叹的HTML5 Canvas动画特效
HTML5的确可以制作出非常绚丽的网页动画效果,尤其是利用HTML5 Canvas特性和HTML5 3D特性,我们更加可以欣赏到超酷的动画特效.今天我从html5tricks网站上整理了8款令人惊叹的 ...
- 9款赏心悦目的HTML5/CSS3应用特效
经过几天的收集,在html5tricks网站上又增加了不少HTML5的教程和应用,今天我把几款赏心悦目的HTML5/CSS3应用特效总结了一下,一共9款HTML5特效,分享给大家. 1.HTML5 W ...
随机推荐
- 用PS给图标添加外发光效果
最近在做app的时候用到了图标需要根据点击和非点击显示两种状态(原始状态和外发光状态). 如下图: 没办法,因为这是毕业设计的东西,总不能叫同事帮忙处理下.所以自己充当了回美工. 做法如下: 1.打开 ...
- 【贪心】【堆】Gym -100956D - Greedy Game
题意:给定n个物品,每个物品对于A和B来说具有不同的价值,记为ai,bi,两人交替取,A先手,A总是贪心地取当前剩下的物品中,对于他价值最高的,如果有多个,则任取一个.问B在最坏情况下,能取到的物品的 ...
- UVa 10911 - Forming Quiz Teams
题目大意:给出2*n个点,将这些点配成n对,使得所有点对中两点的距离之和尽量小. 用一个整数的二进制形式表示一个集合的子集,以每个集合为状态进行状态转移.具体参见<算法竞赛入门经典>9.5 ...
- TortoiseSVN使用简介(收藏)
TortoiseSVN使用简介 1.安装及下载client 端 2.什么是SVN(Subversion)? 3.为甚么要用SVN? 4.怎么样在Windows下面建立SVN Repository? 5 ...
- 对js原型对象的拓展和原型对象的重指向的区别的研究
我写了如下两段代码: function Person(){} var p1 = new Person(); Person.prototype = { constructor: Person, name ...
- Hdu-2112 HDU Today (单源多点最短路——Dijsktra算法)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2112 题目大意:给你N个公交车站,起点,终点,各站之间的距离,求起点到终点之间的最短距离.(起点终点相 ...
- 利用Navicat实现MySQL数据库结构对比和同步
在生产环境中,我们总会因为这样或那样的原因导致主从不同步,亦或者是测试环境要和生产环境进行同步,利用Navicat结构同步工具,不但能找出库结构差异,还可以针对create.modify.drop等进 ...
- 数组(Array)资源
上面的程序中在Java代码中定义了两个数组,Android并不推荐在Java代码中定义数组,因为Androd允许通过资源文件来定义数组资源. Android采用位于/res/values目录下的arr ...
- ThinkPHP URL伪静态、路由规则、重写、生成
一.URL规则 1.默认是区分大小写的 2.如果我们不想区分大小写可以改配置文件 'URL_CASE_INSENSITIVE'=>true,//url不区分大小写 ...
- void 0 === undefined
http://www.cnblogs.com/fsjohnhuang/p/4146506.html