How to create water Ripple effect using HTML5 canvas

Water ripple effect with HTML5. Today we continue JavaScript examples, and our article will about using javascript in modeling of water effects. This will emulation of water drops at images. We should click at image in desired place to see this effect. Sometimes we can create very interesting solutions using ordinary Javascript (of course for HTML) 🙂

Here are sample and downloadable package:

Live Demo

[sociallocker]

download in package

[/sociallocker]


Ok, download the example files and lets start coding !


Step 1. HTML

As usual, we start with the HTML.

This is our main page code with all samples.

index.html

01 <!DOCTYPE html>
02 <html>
03     <head>
04         <meta charset=utf-8 />
05         <title>Water drops effect</title>
06         <link rel="stylesheet" href="css/main.css" type="text/css" />
07         <script src="js/vector2d.js" type="text/javascript" charset="utf-8"></script>
08         <script src="js/waterfall.js" type="text/javascript" charset="utf-8"></script>
09     </head>
10     <body>
11         <div class="example">
12             <h3><a href="#">Water drops effect</a></h3>
13             <canvas id="water">HTML5 compliant browser required</canvas>
14             <div id="switcher">
15                 <img onclick='watereff.changePicture(this.src);' src="data_images/underwater1.jpg" />
16                 <img onclick='watereff.changePicture(this.src);' src="data_images/underwater2.jpg" />
17             </div>
18             <div id="fps"></div>
19         </div>
20     </body>
21 </html>

Step 2. CSS

Here are used CSS styles.

css/main.css

01 body{background:#eee;margin:0;padding:0}
02 .example{background:#FFF;width:600px;border:1px #000 solid;margin:20px auto;padding:15px;-moz-border-radius: 3px;-webkit-border-radius: 3px}
03 #water {
04     width:500px;
05     height:400px;
06     displayblock;
07     margin:0px auto;
08     cursor:pointer;
09 }
10 #switcher {
11     text-align:center;
12     overflow:hidden;
13     margin:15px;
14 }
15 #switcher img {
16     width:160px;
17     height:120px;
18 }

Step 3. JS

Here are our main control JS file.

js/main.js

001 function drop(x, y, damping, shading, refraction, ctx, screenWidth, screenHeight){
002     this.x = x;
003     this.y = y;
004     this.shading = shading;
005     this.refraction = refraction;
006     this.bufferSize = this.x * this.y;
007     this.damping = damping;
008     this.background = ctx.getImageData(0, 0, screenWidth, screenHeight).data;
009     this.imageData = ctx.getImageData(0, 0, screenWidth, screenHeight);
010     this.buffer1 = [];
011     this.buffer2 = [];
012     for (var i = 0; i < this.bufferSize; i++){
013         this.buffer1.push(0);
014         this.buffer2.push(0);
015     }
016     this.update = function(){
017         for (var i = this.x + 1, x = 1; i < this.bufferSize - this.x; i++, x++){
018             if ((x < this.x)){
019                 this.buffer2[i] = ((this.buffer1[i - 1] + this.buffer1[i + 1] + this.buffer1[i - this.x] + this.buffer1[i + this.x]) / 2) - this.buffer2[i];
020                 this.buffer2[i] *= this.damping;
021             else x = 0;
022         }
023         var temp = this.buffer1;
024         this.buffer1 = this.buffer2;
025         this.buffer2 = temp;
026     }
027     this.draw = function(ctx){
028         var imageDataArray = this.imageData.data;
029         for (var i = this.x + 1, index = (this.x + 1) * 4; i < this.bufferSize - (1 + this.x); i++, index += 4){
030             var xOffset = ~~(this.buffer1[i - 1] - this.buffer1[i + 1]);
031             var yOffset = ~~(this.buffer1[i - this.x] - this.buffer1[i + this.x]);
032             var shade = xOffset * this.shading;
033             var texture = index + (xOffset * this.refraction  + yOffset * this.refraction * this.x) * 4;
034             imageDataArray[index] = this.background[texture] + shade;
035             imageDataArray[index + 1] = this.background[texture + 1] + shade;
036             imageDataArray[index + 2] = 50 + this.background[texture + 2] + shade;
037         }
038         ctx.putImageData(this.imageData, 0, 0);
039     }
040 }
041 var fps = 0;
042 var watereff = {
043     // variables
044     timeStep : 20,
045     refractions : 2,
046     shading : 3,
047     damping : 0.99,
048     screenWidth : 500,
049     screenHeight : 400,
050     pond : null,
051     textureImg : null,
052     interval : null,
053     backgroundURL : 'data_images/underwater1.jpg',
054     // initialization
055     init : function() {
056         var canvas = document.getElementById('water');
057         if (canvas.getContext){
058             // fps countrt
059             fps = 0;
060             setInterval(function() {
061                 document.getElementById('fps').innerHTML = fps / 2 + ' FPS';
062                 fps = 0;
063             }, 2000);
064             canvas.onmousedown = function(e) {
065                 var mouse = watereff.getMousePosition(e).sub(new vector2d(canvas.offsetLeft, canvas.offsetTop));
066                 watereff.pond.buffer1[mouse.y * watereff.pond.x + mouse.x ] += 200;
067             }
068             canvas.onmouseup = function(e) {
069                 canvas.onmousemove = null;
070             }
071             canvas.width  = this.screenWidth;
072             canvas.height = this.screenHeight;
073             this.textureImg = new Image(256, 256);
074             this.textureImg.src = this.backgroundURL;
075             canvas.getContext('2d').drawImage(this.textureImg, 0, 0);
076             this.pond = new drop(
077                 this.screenWidth,
078                 this.screenHeight,
079                 this.damping,
080                 this.shading,
081                 this.refractions,
082                 canvas.getContext('2d'),
083                 this.screenWidth, this.screenHeight
084             );
085             if (this.interval != null){
086                 clearInterval(this.interval);
087             }
088             this.interval = setInterval(watereff.run, this.timeStep);
089         }
090     },
091     // change image func
092     changePicture : function(url){
093         this.backgroundURL = url;
094         this.init();
095     },
096     // get mouse position func
097     getMousePosition : function(e){
098         if (!e){
099             var e = window.event;
100         }
101         if (e.pageX || e.pageY){
102             return new vector2d(e.pageX, e.pageY);
103         else if (e.clientX || e.clientY){
104             return new vector2d(e.clientX, e.clientY);
105         }
106     },
107     // loop drawing
108     run : function(){
109         var ctx = document.getElementById('water').getContext('2d');
110         watereff.pond.update();
111         watereff.pond.draw(ctx);
112         fps++;
113     }
114 }
115 window.onload = function(){
116     watereff.init();
117 }

As you can see- I using vector2d function here. This function available in ‘vector2d.js’ (in our package). Another code – pretty difficult – pure mathematics. But you are welcome to make experiments here.


Live Demo

Conclusion

Hope that you was happy to play with it. I hope that water drops looks fine 🙂 If is you were wondering – do not forget to thank. I would be grateful for your interesting comments. Good luck!