How to create water Ripple effect using HTML5 canvas

How to create water Ripple effect using HTML5 canvas

18 190675
Water drops effect

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

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8 />
        <title>Water drops effect</title>
        <link rel="stylesheet" href="css/main.css" type="text/css" />
        <script src="js/vector2d.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/waterfall.js" type="text/javascript" charset="utf-8"></script>
    </head>
    <body>
        <div class="example">
            <h3><a href="#">Water drops effect</a></h3>
            <canvas id="water">HTML5 compliant browser required</canvas>
            <div id="switcher">
                <img onclick='watereff.changePicture(this.src);' src="data_images/underwater1.jpg" />
                <img onclick='watereff.changePicture(this.src);' src="data_images/underwater2.jpg" />
            </div>
            <div id="fps"></div>
        </div>
    </body>
</html>

Step 2. CSS

Here are used CSS styles.

css/main.css

body{background:#eee;margin:0;padding:0}
.example{background:#FFF;width:600px;border:1px #000 solid;margin:20px auto;padding:15px;-moz-border-radius: 3px;-webkit-border-radius: 3px}
#water {
    width:500px;
    height:400px;
    display: block;
    margin:0px auto;
    cursor:pointer;
}
#switcher {
    text-align:center;
    overflow:hidden;
    margin:15px;
}
#switcher img {
    width:160px;
    height:120px;
}

Step 3. JS

Here are our main control JS file.

js/main.js

function drop(x, y, damping, shading, refraction, ctx, screenWidth, screenHeight){
    this.x = x;
    this.y = y;
    this.shading = shading;
    this.refraction = refraction;
    this.bufferSize = this.x * this.y;
    this.damping = damping;
    this.background = ctx.getImageData(0, 0, screenWidth, screenHeight).data;
    this.imageData = ctx.getImageData(0, 0, screenWidth, screenHeight);
    this.buffer1 = [];
    this.buffer2 = [];
    for (var i = 0; i < this.bufferSize; i++){
        this.buffer1.push(0);
        this.buffer2.push(0);
    }
    this.update = function(){
        for (var i = this.x + 1, x = 1; i < this.bufferSize - this.x; i++, x++){
            if ((x < this.x)){
                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];
                this.buffer2[i] *= this.damping;
            } else x = 0;
        }
        var temp = this.buffer1;
        this.buffer1 = this.buffer2;
        this.buffer2 = temp;
    }
    this.draw = function(ctx){
        var imageDataArray = this.imageData.data;
        for (var i = this.x + 1, index = (this.x + 1) * 4; i < this.bufferSize - (1 + this.x); i++, index += 4){
            var xOffset = ~~(this.buffer1[i - 1] - this.buffer1[i + 1]);
            var yOffset = ~~(this.buffer1[i - this.x] - this.buffer1[i + this.x]);
            var shade = xOffset * this.shading;
            var texture = index + (xOffset * this.refraction  + yOffset * this.refraction * this.x) * 4;
            imageDataArray[index] = this.background[texture] + shade;
            imageDataArray[index + 1] = this.background[texture + 1] + shade;
            imageDataArray[index + 2] = 50 + this.background[texture + 2] + shade;
        }
        ctx.putImageData(this.imageData, 0, 0);
    }
}
var fps = 0;
var watereff = {
    // variables
    timeStep : 20,
    refractions : 2,
    shading : 3,
    damping : 0.99,
    screenWidth : 500,
    screenHeight : 400,
    pond : null,
    textureImg : null,
    interval : null,
    backgroundURL : 'data_images/underwater1.jpg',
    // initialization
    init : function() {
        var canvas = document.getElementById('water');
        if (canvas.getContext){
            // fps countrt
            fps = 0;
            setInterval(function() {
                document.getElementById('fps').innerHTML = fps / 2 + ' FPS';
                fps = 0;
            }, 2000);
            canvas.onmousedown = function(e) {
                var mouse = watereff.getMousePosition(e).sub(new vector2d(canvas.offsetLeft, canvas.offsetTop));
                watereff.pond.buffer1[mouse.y * watereff.pond.x + mouse.x ] += 200;
            }
            canvas.onmouseup = function(e) {
                canvas.onmousemove = null;
            }
            canvas.width  = this.screenWidth;
            canvas.height = this.screenHeight;
            this.textureImg = new Image(256, 256);
            this.textureImg.src = this.backgroundURL;
            canvas.getContext('2d').drawImage(this.textureImg, 0, 0);
            this.pond = new drop(
                this.screenWidth,
                this.screenHeight,
                this.damping,
                this.shading,
                this.refractions,
                canvas.getContext('2d'),
                this.screenWidth, this.screenHeight
            );
            if (this.interval != null){
                clearInterval(this.interval);
            }
            this.interval = setInterval(watereff.run, this.timeStep);
        }
    },
    // change image func
    changePicture : function(url){
        this.backgroundURL = url;
        this.init();
    },
    // get mouse position func
    getMousePosition : function(e){
        if (!e){
            var e = window.event;
        }
        if (e.pageX || e.pageY){
            return new vector2d(e.pageX, e.pageY);
        } else if (e.clientX || e.clientY){
            return new vector2d(e.clientX, e.clientY);
        }
    },
    // loop drawing
    run : function(){
        var ctx = document.getElementById('water').getContext('2d');
        watereff.pond.update();
        watereff.pond.draw(ctx);
        fps++;
    }
}
window.onload = function(){
    watereff.init();
}

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!

SIMILAR ARTICLES

Understanding Closures

0 24640

18 COMMENTS

  1. it works somehow on firefox 4 but doesnt work on chrome 12 or it works EXTREMELY SLOW like … 1.5 fps :)
    still good one :D

  2. Hi

    This is cool but I am no coder. How do I insert this into my website? Between the HEADS or just where I want it?

    Thanks

  3. This is cool, thanks
    Is there any way to make “rain”.
    Ie: say make a drop fall on random part of the image every second?
    Thanks

    • Hi Ben,
      Pay attention to function
      canvas.onmousedown
      Generally, you have to start own periodical function (with setInterval), and – execute the same functionality as in canvas.onmousedown (but you have to use random positions here)

    • Hi Guy,
      It didn’t work for you where exactly? At your own computer (localhost) or at our website too?

  4. Hi!
    Thanks for a beautiful effect! I have a question for you:
    How can I make the wave proportionally higher because my canvas size is 909 to 1260 p.? … Also how can I do during a wave effect to operate up to 8 seconds after the click. Where are given these parameters?
    Thanks in advance for your answer!

  5. This is great code. Is there a way to trigger it with an input signal from the computer’s internal microphone? I’m working on an interactive sound page, and these ripples are a great example of sound dispersal.

    • Hi Matt,
      I think that this is possible, however it will require you to learn some basics of how to work with the microphone (with JS).

  6. How do I do something like this?? Or where could I find ready made copy and paste coding for something like this?

    http://www.theripplesproject.org/pond

    I have ZERO experience in coding. I just really want a peaceful pond that when you move your curser over it it ripples….

    • Hi Candi,
      This is flash object, so you need to have some experience to work with flash to repeat this application.

Leave a Reply