不知从什么时候我们已经没有了淋雨的勇气、不知从什么时候我们已经不敢享受雨水的甘甜、不知从什么时候我们感受不到那雨声的清脆。桃花已开但我们却嗅不到他的清香、春雨已来但我们却想方设法将其遮挡、湖水已涨但我们却失去了赏鱼的闲情雅致。时光让我们学到了很多、时光也让我们失去了很多。成天奔波的我们从来不舍得停下脚步,因为今天的我们已经意识到了一寸光阴一寸金的真正内涵。为了冰箱里的那块面包,我们每天就这样奔波着、忙碌着......
停止牢骚,言归正题。
这篇blog描写的是一个动态水波的效果,主要是通过HTML5里面的canvas来实现。整片文章属于原创若有相同纯属有缘,但是里面的核心水波原理并非本人原创是参考网上很多关于水波原理进行综合而成,所以在这里要谢谢网上那些贡献知识的人。闲话少说,先看实现的效果如图一:
图一
考虑到性能问题,建议图片不要太大,本例子用400*300的图片在chrome和firefox下还算流畅,但是safari相对较卡。把图片扩大2倍至800*600就比较卡,效果不是很好。下面将代码贴出共大家一起交流学习。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>春天,陪你一起去赏雨——HTML5下雨效果</title> <style type="text/css"> #box{ border:2px solid #f60; margin:0 auto; } </style> <script> var floor = Math.floor;//向下取整 var canvas;//画布 var context;//画布上下文 var width;//背景图片、画布宽 var height;//背景图片、画布宽 var size;//像素点个数、width*height var nextPoint = [];//下一振幅 var prePoint = [];//上一振幅 var tempPoint = [];//临时存储 var imgData;//背景图片数据 var speed = 100;//下雨大小,默认是一秒钟一个雨滴 var weight = 1000;//雨滴的大小 /** author:qingfeilee date:2012-03-24 description:开始启动程序 **/ function start() { initImage("background.jpg"); } /** author:qingfeilee date:2012-03-24 description:初始化图片信息 **/ function initImage(src) { var img = new Image(); img.src = src; img.onload = function() { init(img); }; } /** author:qingfeilee date:2012-03-24 description:初始化系统函数 **/ function init(img){ initSize(img); initPoint(); initCanvas(); loadImage(img); } /** author:qingfeilee date:2012-03-24 description:绘制图片函数 **/ function loadImage(img){ context.drawImage(img, 0, 0); imgData = context.getImageData(0, 0, width, height); setInterval(spread, 1000/60); setInterval(rain, speed); } /** author:qingfeilee date:2012-03-24 description:初始化画布信息 **/ function initCanvas(){ canvas = document.getElementById('ripper'); context =canvas.getContext('2d'); canvas.width = width; canvas.height = height; canvas.onclick = function(e) { setDropPoint(floor(e.clientX-(document.body.clientWidth - width)/2), floor(e.clientY - (document.body.clientHeight - height) / 2), 15000); } } /** author:qingfeilee date:2012-03-24 description:设置画布宽高及画布像素数 **/ function initSize(img){ width = img.width; height = img.height; document.getElementById("box").style.width = width+"px"; document.getElementById("box").style.height = height+"px"; size = width*height; } /** author:qingfeilee date:2012-03-24 description:初始化存储图像前一个和后一个点的数组 **/ function initPoint(){ for (var i = 0; i < size; i++) { nextPoint.push(0); prePoint.push(0); } } /** author:qingfeilee date:2012-03-24 description:一石激起千层浪,设置波动点及注入的能量 其中x表示物体进入水面的X坐标,Y表示物体进入水面的Y坐标,power表示物体的能量大小 **/ function setDropPoint(x, y, power) { if (x < 2 || x > width - 2 || y < 1 || y > height - 2) return; var i = x + y * width; nextPoint[i] += power; nextPoint[i - 1] -= power; } /** author:qingfeilee date:2012-03-24 description:核心算法,处理像素的波动效果 PS:该算法非原创,借鉴网络上多个版本算法综合 **/ function spread() { var img = context.getImageData(0, 0, width, height), data = img.data; //平均一下各个点的能量 for (var i = width + 1; i < size - width - 1; i += 2) { for (var x = 1; x < width - 1; x++, i++) { nextPoint[i] = (nextPoint[i] + nextPoint[i + 1] + nextPoint[i - 1] + nextPoint[i - width] + nextPoint[i + width]) / 5; } } //渲染除了第一行、最后一行、第一列、最后一列外的所有点 for (var i = width + 1; i < size - width - 1; i += 2) { for (var x = 1; x < width - 1; x++, i++) { //水波振幅线性公式参考的是网络上的一些研究文献得出的 prePoint[i] = (nextPoint[i - 1] + nextPoint[i + 1] + nextPoint[i + width] + nextPoint[i - width])/2 - prePoint[i]; var ti = i + floor((prePoint[i - 2] - prePoint[i]) * 0.08) + floor((prePoint[i - width] - prePoint[i]) * 0.08) * width; ti = ti < 0 ? 0 : ti > size ? size: ti; var light = prePoint[i] * 2.0 - prePoint[i - 2] * 0.6; light = light < -10 ? -10 : light > 100 ? 100 : light; //之所以是i*4是因为canvas获取的data数据每四个值表示一个像素包括分别是红/绿/蓝/透明,要想了解更多关于canvas的请参看我的另一篇blog:http://blog.csdn.net/qingfeilee/article/details/7233683 data[i * 4] = imgData.data[ti * 4] + light; data[i * 4 + 1] = imgData.data[ti * 4 + 1] + light; data[i * 4 + 2] = imgData.data[ti * 4 + 2] + light; //波能渐渐衰减 prePoint[i] -= prePoint[i]>>5; } } tempPoint = nextPoint; nextPoint = prePoint; prePoint = tempPoint; context.putImageData(img, 0, 0); } function rain(){ setDropPoint(floor(Math.random()*width), floor(Math.random()*height), floor(Math.random()*weight)); } function setWeight(weight){ this.weight = weight; } window.addEventListener("load", start, true); </script> </head> <body> <div id="box"> <canvas id="ripper" style="width:100%;height:100%; "></canvas> <div align="center"> <button onclick = "setWeight('1000')">小雨</button> <button onclick = "setWeight('10000')">中雨</button> <button onclick = "setWeight('20000')">大雨</button> </div> </div> <div><h5 style = "text-align:center"><a href = 'http://blog.csdn.net/qingfeilee/'>阿飞blog</a></h5></div> </div> </body> </html>
核心算法的解释,在我们利用这个水波原理解析图像的时候我们只需要获取除了第一行、最后一行、第一列、最后一列的像素点即可,如图二:
图二
本Demo中还需要一张背景图片,注:图片不能太大、否则画面将比较不流畅。本文的背景图片如图三:
图三
由代码注释较详细,具体就不再赘述。倘若对Canvas不太了解希望我的另外一篇blog《通过小画板认识Canvas》能够帮助到你。哪位大牛有好的算法实现希望能够多多交流,共同学习,共同进步,欢迎拍砖。
本文乃原创Demo,转载请注明出处:http://blog.csdn.net/qingfeilee/article/details/7394735
使用代码请保留作者署名,O(∩_∩)O谢谢
本Demo的附件下载地址:http://download.csdn.net/detail/qingfeilee/4174536
备注:本代码直接在chrome和firefox下面运行无法运行需要放到服务器里通过http://ip的形式来访问才行,safari可以直接访问但是运行效率较低。之所以前两者不能直接运行的,是因为chrome和firefox两家浏览器的安全策略所致getImageData()不能获取图片数据的原因,但是通过网络即可。