Эффект "стекания" на JS (canvas)
12.03.2013 15:32
0Эффект выражается в том, что изображение начинает "течь" вниз, как вода стекает с вертикальной поверхности.
Демо
Как работает данный эффект?
поведение
Капля жидкости будет вести себя примерно так: из-за неравномерности смачивания поверхности и других факторов + действующей на нее силе тяжести какие-то части ее будут стекать быстрее, какие-то медленнее. Однако, за счет поверхностного натяжения она будет вести себя как цельный объект - т.е. не будет разваливаться на отдельные капли (крайние случай рассматривать не будем).Причем стекание частей в каждый момент времени неравномерен - т.е. если например одна из частей стекавшая до этого быстрее наткнулась на что-то ей мешающее, она может замедлиться и будет опережена другой частью, двигающейся до этого медленнее.
моделирование
При каждом рендеринге кадра делается следующее:- Массив сдвигов, количество элементов в котором равно количеству пикселей по горизонтали - это наши "части объекта" - заполняется случайными значениями от 0 - maxStep, на эти значения будет сдвигаться соответствующие столбцы вниз - т.е. если например в массиве steps[4] = 3, это значит что столбец с координатой X = 4 будет сдвинут вниз на 3 пикселя.
- Все столбцы изображения сдвигаются на число пикселей из массива steps: освободившееся сверху место заполняется черным цветом, а на все перенесенные вниз пиксели накладывается эффект затухания (просто для красоты) - это имитирует различную скорость стекания.
maxStep не должен быть слишком большой - иначе наш объект "развалится". - Поверх полученного изображения накладывается исходное изображение с учетом прозрачности - иначе картинка просто стечет и останется черный экран, а так она будет течь (ну или капать) бесконечно.
По скольку массив заполняется случайными значениями при каждом кадре - это имитирует изменения скорости течения частей - т.е то однни быстрее то другие.
Вот и все!
Код эффекта
Для отрисовки используется библиотека canvas.jsfunction drip( id ) { var bb = new DBUF( id ); bb.clear(); var ibb = null; var play = false; var rrID = null; drip.isPlaying = function() { return play; }; drip.start = function() { if ( play ) return; play = true; renderer(); }; drip.stop = function() { if ( !play ) return; play = false; if ( rrID ) { bb.cancelRedraw( rrID ); rrID = null; } }; var xa = 0, ya = 0; drip.setImage = function( url, noAni ) { var img = new Image(); img.onload = function() { ibb = new DBUF( "imgbb", this.width, this.height ); ibb.drawImage( img, 0, 0 ); xa = Math.floor( ( bb.w - ibb.w ) / 2 ); ya = Math.floor( ( bb.h - ibb.h ) / 2 ); if ( xa < 0 ) xa = 0; if ( ya < 0 ) ya = 0; drip.stop(); if ( noAni ) doDrip(); else drip.start(); }; img.src = url; }; var steps = new Array( bb.w ); var maxStep = 25; var minStep = 1; var fade = 0.8; var maxStepH = maxStep * 0.5; function countSteps() { for ( var i = 0; i < bb.w; i++ ) { steps[i] = Math.floor( maxStepH - Math.random() * maxStep ); if ( i > 0 && i % 2 == 0 ) steps[i] = steps[i-1]; if ( steps[i] < minStep ) steps[i] = minStep; if ( steps[i] > maxStep ) steps[i] = maxStep; } } function doDrip() { var x, y, p; if ( !ibb ) return; countSteps(); for ( x = 0; x < bb.w; x++ ) { var s = steps[x]; for ( y = bb.h - s - 1; y >= 0; y-- ) { p = bb.getPixel( x, y ); p.r = ( p.r * fade ) & 255; p.g = ( p.g * fade ) & 255; p.b = ( p.b * fade ) & 255; bb.setPixel( x, y + s, p.r, p.g, p.b, 255 ); } for ( y = 0; y < s; y++ ) bb.setPixel( x, y, 0, 0, 0, 255 ); } for( y = 0; y < ibb.h && y < bb.h; y++ ) { for ( x = 0; x < ibb.w && x < bb.w; x++ ) { p = ibb.getPixel( x, y ); if ( p.a > 0 ) { bb.mergePixel( x + xa, y + ya, p.r, p.g, p.b, p.a ); } } } bb.put(); } var frameLength = 150; var lastFrame = 0; function renderer() { rrID = null; if ( !play ) return; var now = Date.now(); if ( now - lastFrame > frameLength ) { lastFrame = now; doDrip(); } rrID = bb.requestRedraw( renderer ); } }
12.03.2013, Protocoder