Water ripple FX with Canvas and Javascript

It’s not new and it was done many, many times with Java Applets: the Water Ripple effect! Yeay! I created one some years ago since I just like to play with it so much. Nowadays we have the HTML5 canvas but also fast javascript engines . I still wanted to experiment with the HTML5 canvas anyway so I decided to port my applet to a canvas.

Check the Water Ripple Effect here!

In this article I will show the effect, explain how the effect works and also how you can reuse the highly configurable javascript classes. You could use it for nice cheesy banner effects or just to impress your site visitors ;). Anyway… lets check it out!

The water ripple canvas

It’s best to check this canvas effect on a browser that supports HTML5 canvas (like Google Chrome, Safari, Firefox or IE9)! I recommend to view it on Firefox 4. At time of writing it’s by far the best performing browser for this algorithm. For you people that found this blog with another browser, here’s a screen shot image:

The water ripple effect mimics a top view on water with water rippling and refraction of light through the water. On my implementation you can use your mouse like it’s your finger that touches the water. Pressing the mouse button gives you a bigger ‘finger’. And… there’s no need for performing your rain dance anymore. Just adjust the “Raindrops per second” slider to make raindrops fall on the canvas. If you’re like me, you can spend hours moving your mouse around on the canvas with big eyes and a big smile on your face ;).

Update (March 8th, 2011): I implemented the use of requestAnimationFrame (Read more about it in this article by Paul Irish). It triggers a callback function just like setTimeout would. But now it’s not the time but your browser telling the best time to render the water pixels on the canvas. Also note the ‘FPS Canvas’ for going up after you switched to another tab and back to the water canvas. The browser knows it’s no use redrawing the canvas when no-one is watching it and so preventing your CPU/GPU fans from going mental for nothing ;).

Pimp my site… (with a swimming pool)

How should you add the effect to your site? First of all, the code and the best/preferred way to implement it described here may be updated at any time in the future.  So check back this section when you decide to use the effect in the future. Next, the code will be officially open sourced and will be put on Github when I have time to do this. It’s best to use that code from then on.

To use the code you should put just one javascript file on your web server: watercanvas.js. Then you should include this script in your HTML file’s <head> code:

<script src="watercanvas.js"></script>

Now you need to create some location (a <div>) where the WaterCanvas can insert it’s to be created canvas element. Place this div at the location between the <body> tags where you want the canvas to show up.

...
<div id="waterHolder"></div>
...

Give it an ID so we are able to point to it in javascript in the following step. We are now ready to create and init the necessary objects. In the <head> you should now add a <script> section. Add the following javascript code in it:

<script>
var width = 350;
var height = 275;

function init(){
	var waterModel = new WaterModel(width, height, {
			resolution:2.0,
			interpolate:false,
			damping:0.985,
			clipping:5,
			evolveThreshold:0.05,
			maxFps:30,
			showStats:true
		});
	var waterCanvas = new WaterCanvas(width, height,
		"waterHolder", waterModel, {
			backgroundImageUrl:"images/yourimage.jpg",
			lightRefraction:9.0,
			lightReflection:0.1,
			maxFps:20,
			showStats:true
		});
}
</script>

We created an init function which we will soon call at the document loaded event. When the init function is called, two javascript objects will be created:

  • A WaterModel object:  A model object which is responsible for holding, manipulating and evolving the water ripple data. The model does not need to use the same amount of pixels as the canvas. You should use the same width and hight as for the WaterCanvas but you can set a resolution and use bilinear interpolation.
  • A WaterCanvas object: A view object which is responsible for applying the water ripple data state to an image and therefor is also responsible for the reflection and refraction effects.

See the javascript jsdoc comments in the javascript files for descriptions of all parameters and some more explanation. In the example you can use the sliders to update settings in the models and experiment with it. It’s a great way to find the settings you want to use.

To finish it we need to call the init method when the DOM is loaded so we are sure the div we created before is available (If you know how to work with javascript and DOM loading timings, you can do this in better/faster ways. For the sake of simplicity I’m using the plain old body onload here.):

<body onload="init()">

Now upload all files to your server and open the HTML file  in your browser… Wait, nothing is happening!? Of course, we need to “touch” the WaterModel to see the effect in action:

<script>
...
var finger = [
	[0.5, 1.0, 0.5],
	[1.0, 1.0, 1.0],
	[0.5, 1.0, 0.5]
];

waterModel.touchWater(100, 100, 1.5, finger);
</script>

The touchWater function needs an X and Y position, a pressure multiplier and a 2D array pattern which represents a “finger” or “raindrop”. This is actually the basis of how to use the Water Canvas… Ok, a little more insight in the code above:

You need to create a 2D array which the touchWater function is using to apply to the WaterModel. This is powerful since you can apply pressure to the WaterModel in any shape you like! See my example for how I use a util function which is also in watercanvas.js (util functions are at the bottom of the file). You can use it to create a 2D array from a (small) canvas which you can draw to in gray scale values to create your own custom shapes.

You can also make your mouse pointer touch the WaterModel. You need to use mouse EventListeners to track your mouse. Again, I created an out-of-the-box util function for you. Check the code of my example to find out how to do this.

The water ripple FX algorithm and javascript

So what’s the trick? What does make this water effect work?

There’s actually a very good tutorial by Hugo Elias about the functionality of the WaterModel which I used when I create my first implementation in a Java Applet. So I won’t go to deep into that. In short; for every position in a 2D array the algorithm looks in the surrounding 2D array positions for the float values, then applies some devisions, et voila, there’s a new value for the current position. You need to use two 2D arrays. One for the current state and one for the new state to calculate. All newly calculated positions will be put in a second 2D array buffer. After all positions are rendered the buffers will be swapped before the next calculation round starts.

The WaterCanvas uses the state of the data in the WaterModel at a frame render moment. It reads the positions of the WaterModel’s 2D array buffer and uses each value to manipulate the pixels of the WaterCanvas’s canvas pixels. There are two different things I applied to these pixels;

  • reflection of light on the water
  • refraction of light through the water

For reflection, for every pixel of the canvas we get a value from the WaterModel. When the WaterModel returns a positive value, we’ll make the pixel color a little lighter by the amount of the value, when negative, a little darker by the amount of the value. Refraction is done in almost the same way. When the value is positive get the pixel of the current position with the positive amount added. For a negative value automatically the current position minus the absolute value is used. This is in short how it works. Wanna know more? Check out the code!

Splish splash… The conclusion

So far this canvas experiment on realtime pixel manipulation works. But, when compared to some Java Applets I should conclude Javascript is just not as fast. On the other hand, canvas’s start faster then Java Applets without Java splash screens (what’s in a word ;)). When you use the Water Canvas you should keep the canvas as small as possible to be sure it performs. Set the max FPS’s to the minimum you find to be OK.

There are some enhancements I may try at some time like using an HTML5 WebWorker for the WaterModel and/or using some WebGL hardware performance optimizations. At this point I really don’t know if WebGL can be used at all for this effect.

I should also take some time to make the Water Canvas degrade gracefuly on old browsers. Currently you will get bothered with an anoying javascript alert when the browser doesn’t support HTML5 canvas. Also, it should be possible to point this script to an <img> tag or another <canvas> tag, where the WaterCanvas class constructor will convert it to a Water Canvas for you.

Let me know what you think of the Water Ripple Canvas or how I can improve the performance of it. OK, I’m off for a swim…

Fast forward to…WebGL

Update (Februari 9th, 2019): It’s been a long time since I implemented this effect. These days WebGL is here in many major browsers to use. Be sure to also check out this WebGL version of the water ripple effect!

45 gedachten aan “Water ripple FX with Canvas and Javascript”

    1. Hi Nicolas, I agree (although the implementation you mention has probably been updated since I’ve seen this one being slower before). The problem with my implementation is probably due to implementing this effect in an Object Oriented (almost MVC) way. I’ve seen an online presentation from Thomas Fuchs recently on high performance Javascript. He has many tips on improving speed which I might one day use to increese the speed. A key for speed is to keep all processing in one loop as much as possible. I will make the code less readable but probably faster.

  1. Hi Almeros,

    The implementation I pointed to was not updated since its original. I think this was the first HTML5 water ripple effect created. That aside, my only point is that this problem only becomes interesting once you consider the efficiency aspect, even if that means you have to forgo a “perfect” design pattern.

  2. Hi Wouter, thanks for your comment. Nice observation of the CPU core usage!

    Now there’s a problem in that you can’t really tell Javascript how and which cores to use, neither ask how many cores there are available on a client. That’s something the browser’s Javascript engine decides/implements. To solve this I might try to use multiple HTML5’s Web workers (but this effect won’t be working in the upcoming IE9 when using WebWorkers, but then again, the Canvas in IE9 preview shows bugs with this effect anyway ;)).

    What browser did you use? I might try to use multiple cores next time I’m working on this effect!

  3. Just wanted to mention that, when using small dimensions your app is quite fast and useful. I knoow its not optimal (like using in a giant and beautyful background or something) and probably not the usage you expected but its a good start.

    Maybe if disabling some features (like clicking on it) it will make it faster a bit? I am having a similar problem with a jquery app I’m using for 3d effects, but its eating my pc quite a bit, so I’m considering purchasing one of those Thomas Fuch’s books because it seem to be very handy.

    Thank you for all the work you had.

  4. Hi Diego Crusius, thanks for your comment and sorry for replying just now.

    If you’re creating 3D effects, be sure to also check out Web-GL for creating 3D effects via your graphics card’s GPU. Nothing will beat that in performance! 🙂
    I personally could also try and use Web-GL for this 2D effect also. But there’s a lot for me to check out (learn how shaders work) and I should probably refactor the current code in total.

  5. Is it possible to get the ripple’s only in a circle?

    i have a image of a cup of coffee.. like to ripple the coffee only.

    thnx!

    1. Hi Veen00,

      I quess there’s two ways doing that, an easy way and a hard way (which will be more realistic).

      For the hard way you should alter my algorithm code and create a circular 2d buffer some way. The water waves will bounce of the sides more realisticly. The easy way (go for this one if you want to use it for fun) should be achieved by using the coffee background in the canvas. Now add a transparent .PNG image with the cup and a transparent cutout of the coffee content on top over the canvas. Make the canvas with ripple effect as small as possible arround the circle. That should do it!

      If you created this, please send me a link to your creation!

  6. Hello, this is a very great program:)

    I have a little question. Is it possible to make the rain drop effect for only a second or two? With a smooth fade out.

    Thank you!

    Best regards,
    tim

    1. Hi Tim, thanks,

      Sure, actually, when you setup a WaterModel and a WaterCanvas just DON’T add a mouse event listener and DON’T add the rain generator. Only create a ‘finger’ in a 2D array like the code above and perform a touchWater on the WaterModel object on the location where you want the ripple to start. Change the settings to let the ripple fade out in about 2 seconds and you’re done (you can use my example’s sliders to figure out what are the best settings for you).

      Nice to know; when your single ripple is done rippling, the WaterModel and Canvas will stop taking resources on your website, keeping the performance for other javascripts optimal.

      Let me know when you have something online! Have fun!

  7. I want to use this effect on my website but I am only an artist and the instructions overwhelm me. Is there a simpler step by step available? A lot of the terms make no sense to me. I don’t code my sites but use a program that generates the code (OK stop hissing and booing 🙂 and I can insert code and don’t know where I would insert this stuff.

    Thanks

    1. Hi Luke,

      First of all, thanks for your interest. I think you’re right that the story above is a bit nerdy ;). There isn’t really a simpler step by step unfortunately. What I can give you is the code for the simplest working html with a water canvas.

      Put it all on a webserver (unzipped ofcourse) and check it out via your webserver’s URL…

      By checking the index.html in a browser from your desktop (via file:// instead of http://) the background.jpg image won’t be read into the canvas due to the Same Origin Policy (security) the canvas element uses (HTML5 spec). There are ways to disable this in your browser though. Google for it.

      You can fiddle with the settings in index.html now and/or change the background image, until you like it!

      Also, please understand that this effect is taking quite a lot of browser resources. Don’t expect it to perform full screen (or even half) on your webpage or something! Putting it on a site’s logo should however work fine on modern browsers.

  8. Thumbs up for your water canvas.

    I’m sorry this might sound as a easy step for most, but I have just started.
    Got the first one to work, but how do I get a second water canvas on the same page.?

    Many thanks in advance.

  9. Hello !
    I just wanted to congatulate you on your work, it is quite interresting !
    I have a question on your script, is it possible not to use a background image, but an transaprent image instead. I am using a phpbb forum, and i would like to use your script on my logo.
    What need to be changed in your script to make it work properly ?
    Thanks for your time…

  10. Hi There

    While creating a 3d cube canvas effect I found out how slow manipulating the raw pixels in javascript is, I found a faster way was to draw cropped areas of the source image on the canvas and distort them line by line, this massivily sped up my effect because the canvas drawing is hardware accelerated. Would love to see if your speed increases doing it this way. I think the demo is great.

    Andy

    1. Hi Andy, this water effect unfortunately does really need full X by Y pixel manipulation making it a slow algorithm. The only real CPU cycle optimization thing I could do is use requestAnimationFrame. Actually I’ve seen a faster implementation of this algorithm somewhere, it’s less flexible though since it didn’t seem to use a backbuffer (one time XxY instead of two times XxY). But full screen will be to far fetched these days… perhaps WebGL shaders can hardware excellerate these kind of effects.

    1. Hi Amit,

      Have you tried putting a div over the canvas with CSS for the lotus image? And with HTML it should be very easy to add ‘wall’ images around the canvas 🙂

  11. @Shannon,

    Sure, create another div with its own ID and initiate a new waterModel and waterCanvas. In the waterCanvas you should point to the new div’s ID.

    BUT, don’t expect to much from the performance!

  12. Awesome!!! I’m using this now. I wanted to know if there is a way to spread the drops out a little on the x or width coordinates so it doesn’t look so ‘rain drop-ish’ I’m trying to use this over an image of a body of water. sorry just wanted to know, I know its meant for rain drops. Many thanks!!!

    1. Hi Lance, great that you like it! Unfortunately I don’t really understand what you’re trying to achieve. Just know you’re free to fiddle with the code as you want, so why don’t you just try and experiment? Have fun!

  13. Thanks Almeros! I just wanted to expand the circle by the x value only and not so much by both the X and Y values. I’ll fiddle around. Thanks again for the amazing code! I’ve been searching for js water physics but only have been able to find ripple effects. Anyhow thanks!!!

  14. Hi there Almeros,

    I loved this tutorial and script and added it to a page. I would love to be able to make it “touch” enabled for iPhone’s and iPads etc.. Do you know how I would do this? My first idea was to create other versions of the even listeners for touch but this did not happen, as when I touch the canvas on an iPhone it moves the whole page. I would love to hear your insights there and anything you see to make this available for all devices! Thank you so much.

    Jamie

  15. Hi Jamie, perhaps you could try something with CSS like position:fixed;. For the rest, you should Google it up ;).

    Also please note this will probably work real slow on iOS devices since Apple seems to throttle javascript execution speed :(.

    Hope this helps a bit!

  16. Hi,
    Actually i want this effect on my background image but when m increasing the width and height of the image the ripple effect slows down.

    plzz anyone solve my issue

  17. Congrats on a great Water Ripple effect, the best i cant find arround no only for its quality but also for the easy editable stats. Its simply awesome.

    Also I would like to ask you something, I intend to use it as a background for a logo for a water company webpage, but the wrapper div is using margins to keep centered as (auto) at both sides, so when the mouse is on the waterholder the raindrop or the finger sets to the left by the same amount as the margin. Is there a way to fix this? so the finger sets exactly below the pointer?

    In advance I thank you for any response and shortly I will send you a link so you can see it in action 😉

  18. Hi Almeros,
    I really love your water ripple effect and have been trying to use it in InDesign. I am creating a children’s underwater adventure story which is animated and I think it would be really look awesome to use the water effect on some of the images. I’m having a problem with inDesign as errors arise with the requestAnimationFrame function and also I am not a brilliant programmer (just did a little C# a few years ago). Any advice would be appriciated.

    Marita

  19. Hi! Great work!

    Can I use it with fetch loading site as full screen background placed under contents area and sidebars with content height escalation? Can it fire my CPU?

    Thanks …

    1. Hi Thanks, I don’t really understand what you are trying to do. You could fill up your entire background, sure; it’s a canvas so you can treat it like any other HTML element. But it’ll probably work very slow on lots of your visitors devices. In that way it will heat up your CPU ;). Use at own risk 😉

  20. Hi Almeros,

    Thanks for developing such a useful script for water effects and I am able to achieve almost everything. I have one problem for which I need your guidance. Can you please help me to figure out a way to keep the Redial background color as white (At the moment, it’s black). I have tried to change the CreateRadialCanvas and able to create the initial effect for water drop:

    radgrad.addColorStop(0.8, ‘rgba(255,0,0,0)’);
    radgrad.addColorStop(0.85,’rgba(255,0,0,.6)’);
    radgrad.addColorStop(0.9, ‘rgba(255,0,0,1)’);
    radgrad.addColorStop(0.95,’rgba(255,0,0,.6)’);
    radgrad.addColorStop(1,’rgba(255,0,0,0)’);

    But the problem here is that the initial color of the initial water effect is black but I wanted to make it white. I have tried to change the code in drawNextFrame function as well but not able to change it. I was hoping that you shall help me to fix this issue. I shared the modified code so that you can take a look and guide me to fix it.

    1. Hi, sorry for not reacting sooner… Have you tried just using a white image as a background? Otherwise, loose the whole radgrad as fillstyle and just add a white background to a canvas. Many tutorials for that on the internet. Hope this helps you out!

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *