JavaScript implementation of the "classic" water ripple demo. Move mouse pointer over the image to get more ripples.
function v(b,c){b<<=0;c<<=0;for(var a=c-i;a<c+i;a++)for(var e=b-i;e<b+i;e++)m[j+a*d+e]+=512}var k=document.getElementById("c"),l=k.getContext("2d"),d=400,w=d>>1,A=d*402*2,j=d,n=d*403,i=3,o,m=[],p=[],q,x;k.width=d;k.height=400;with(l){fillStyle="#a2ddf8";fillRect(0,0,d,400);fillStyle="#07b";save();rotate(-0.785);for(var f=0;f<20;f++)fillRect(-d,f*40,d*3,20);restore()}x=l.getImageData(0,0,d,400);q=l.getImageData(0,0,d,400);for(f=0;f<A;f++)p[f]=m[f]=0;k.onmousemove=function(b){v(b.offsetX||b.layerX,b.offsetY||b.layerY)};setInterval(function(){var b,c,a;b=j;j=n;n=b;b=0;o=j;for(var e=d,g=m,h=o,y=n,r=q.data,s=x.data,t=0;t<400;t++)for(var u=0;u<e;u++){a=g[h-e]+g[h+e]+g[h-1]+g[h+1]>>1;a-=g[y+b];a-=a>>5;g[y+b]=a;a=1024-a;c=p[b];p[b]=a;if(c!=a){c=((u-w)*a/1024<<0)+w;a=((t-200)*a/1024<<0)+200;if(c>=e)c=e-1;if(c<0)c=0;if(a>=400)a=399;if(a<0)a=0;a=(c+a*e)*4;c=b*4;r[c]=s[a];r[c+1]=s[a+1];r[c+2]=s[a+2]}++h;++b}o=h;l.putImageData(q,0,0)},30);var z=Math.random;setInterval(function(){v(z()*d,z()*400)},700)
ZnVuY3Rpb24gdihiLGMpe2I8PD0wO2M8PD0wO2Zvcih2YXIgYT1jLWk7YTxjK2k7YSsrKWZvcih2YXIgZT1iLWk7ZTxiK2k7ZSsrKW1baithKmQrZV0rPTUxMn12YXIgaz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYyIpLGw9ay5nZXRDb250ZXh0KCIyZCIpLGQ9NDAwLHc9ZD4+MSxBPWQqNDAyKjIsaj1kLG49ZCo0MDMsaT0zLG8sbT1bXSxwPVtdLHEseDtrLndpZHRoPWQ7ay5oZWlnaHQ9NDAwO3dpdGgobCl7ZmlsbFN0eWxlPSIjYTJkZGY4IjtmaWxsUmVjdCgwLDAsZCw0MDApO2ZpbGxTdHlsZT0iIzA3YiI7c2F2ZSgpO3JvdGF0ZSgtMC43ODUpO2Zvcih2YXIgZj0wO2Y8MjA7ZisrKWZpbGxSZWN0KC1kLGYqNDAsZCozLDIwKTtyZXN0b3JlKCl9eD1sLmdldEltYWdlRGF0YSgwLDAsZCw0MDApO3E9bC5nZXRJbWFnZURhdGEoMCwwLGQsNDAwKTtmb3IoZj0wO2Y8QTtmKyspcFtmXT1tW2ZdPTA7ay5vbm1vdXNlbW92ZT1mdW5jdGlvbihiKXt2KGIub2Zmc2V0WHx8Yi5sYXllclgsYi5vZmZzZXRZfHxiLmxheWVyWSl9O3NldEludGVydmFsKGZ1bmN0aW9uKCl7dmFyIGIsYyxhO2I9ajtqPW47bj1iO2I9MDtvPWo7Zm9yKHZhciBlPWQsZz1tLGg9byx5PW4scj1xLmRhdGEscz14LmRhdGEsdD0wO3Q8NDAwO3QrKylmb3IodmFyIHU9MDt1PGU7dSsrKXthPWdbaC1lXStnW2grZV0rZ1toLTFdK2dbaCsxXT4+MTthLT1nW3krYl07YS09YT4+NTtnW3krYl09YTthPTEwMjQtYTtjPXBbYl07cFtiXT1hO2lmKGMhPWEpe2M9KCh1LXcpKmEvMTAyNDw8MCkrdzthPSgodC0yMDApKmEvMTAyNDw8MCkrMjAwO2lmKGM+PWUpYz1lLTE7aWYoYzwwKWM9MDtpZihhPj00MDApYT0zOTk7aWYoYTwwKWE9MDthPShjK2EqZSkqNDtjPWIqNDtyW2NdPXNbYV07cltjKzFdPXNbYSsxXTtyW2MrMl09c1thKzJdfSsraDsrK2J9bz1oO2wucHV0SW1hZ2VEYXRhKHEsMCwwKX0sMzApO3ZhciB6PU1hdGgucmFuZG9tO3NldEludGVydmFsKGZ1bmN0aW9uKCl7dih6KCkqZCx6KCkqNDAwKX0sNzAwKQ==
/**
* Water ripple effect.
* Original code (Java) by Neil Wallis
* @link http://www.neilwallis.com/java/water.html
*
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
(function(){
var canvas = document.getElementById('c'),
/** @type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
mapind,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
* (see example here: http://media.chikuyonok.ru/ripple/)
* But I need to draw simple artwork to bypass 1k limitation
*/
with (ctx) {
fillStyle = '#a2ddf8';
fillRect(0, 0, width, height);
fillStyle = '#07b';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
restore();
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 512;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var i, a, b, data, cur_pixel, new_pixel, old_data;
i = oldind;
oldind = newind;
newind = i;
i = 0;
mapind = oldind;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_mapind = mapind,
_newind = newind,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind + i];
data -= data >> 5;
_ripplemap[_newind + i] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++_mapind;
++i;
}
}
mapind = _mapind;
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
disturb(rnd() * width, rnd() * height);
}, 700);
})();