HTML Canvas 中的 Javascript Animation - 无法将动画保留在边框内

Javascript Animation in HTML Canvas - Can't keep animation within borders

本文关键字:动画 保留 边框 中的 Canvas Javascript Animation HTML      更新时间:2023-09-26

我正在使用一个javascript动画,它在html画布中显示水中的涟漪。

这是javascript代码和jfiddle链接

(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,
        ripplemap = [],
        last_map = [],
        ripple,
        texture,
        line_width = 20,
        step = line_width * 2, 
        count = height / line_width;
        
    canvas.width = width;
    canvas.height = height;
    
    
    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] += 128;
            }
        }
    }
    
    /**
     * Generates new ripples
     */
    function newframe() {
        var a, b, data, cur_pixel, new_pixel, old_data;
        
        var t = oldind; oldind = newind; newind = t;
        var i = 0;
        
        // create local copies of variables to decrease
        // scope lookup time in Firefox
        var _width = width,
            _height = height,
            _ripplemap = ripplemap,
            _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++) {
                var _newind = newind + i, _mapind = oldind + i;
                data = (
                    _ripplemap[_mapind - _width] + 
                    _ripplemap[_mapind + _width] + 
                    _ripplemap[_mapind - 1] + 
                    _ripplemap[_mapind + 1]) >> 1;
                data -= _ripplemap[_newind];
                data -= data >> 5;
                _ripplemap[_newind] = 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];
                }
                ++i;
            }
        }
    }
    
    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);
    
})();

问题是涟漪从画布的一侧溢出并在另一侧动画化,我想防止它们移动到相反的一侧。

谢谢!

在边缘停止波浪

要阻止波在边缘的传播,您需要限制添加干扰的函数,使其不会写过边缘。

所以改变...

function disturb(dx, dy) {
    dx <<= 0;
    dy <<= 0;
    for (var y = dy - riprad; y < dy + riprad; y++) {
        for (var x = dx - riprad; x < dx + riprad; x++) {
            ripplemap[oldind + (y * width) + x] += 128;
        }
    }
}

自。。。

function disturb(dx, dy) {
    dx <<= 0;
    dy <<= 0;
    for (var y = dy - riprad; y < dy + riprad; y++) {
        for (var x = dx - riprad; x < dx + riprad; x++) {
            // don't go past the edges.
            if(y >= 0 && y < height && x >= 0 && x < width){
                ripplemap[oldind + (y * width) + x] += 128;
            }
        }
    }
}

并且您还需要更改函数中的波传播 newFrame .传播由 data 中的值控制。值为零表示没有波和传播。因此,测试像素是否在边缘。如果像素是边缘像素,则只需通过将data设置为零来停止波。为了节省 CPU 周期,我添加了 vars hw height - 1width - 1,因此您不必为每个像素执行两次减法。

更改函数中的代码newFrame从...

    for (var y = 0; y < _height; y++) {
        for (var x = 0; x < _width; x++) {
            var _newind = newind + i, _mapind = oldind + i;
            data = (
                _ripplemap[_mapind - _width] + 
                _ripplemap[_mapind + _width] + 
                _ripplemap[_mapind - 1] + 
                _ripplemap[_mapind + 1]) >> 1;
            }

自。。。

    var w = _width - 1; // to save having to subtract 1 for each pixel
    var h = _height - 1; // dito
    for (var y = 0; y < _height; y++) {
        for (var x = 0; x < _width; x++) {
            var _newind = newind + i, _mapind = oldind + i;
            // is the pixel on the edge
            if(y === 0 || x === 0 || y === h || x === w){
                data = 0; // yes edge pixel so stop propagation.
            }else{
                // not on the edge so just do as befor.
                data = (
                    _ripplemap[_mapind - _width] + 
                    _ripplemap[_mapind + _width] + 
                    _ripplemap[_mapind - 1] + 
                    _ripplemap[_mapind + 1]) >> 1;
            }

这并不完美,因为它会抑制从侧面反弹的波浪,但您没有太多的 CPU 时间,因此这是一个很好的解决方案。

一些注意事项。

使用requestAnimationFrame而不是setInterval(run, delay)来计时动画,因为如果设备无法处理 CPU 负载,则会导致某些设备使用您当前拥有的代码使页面崩溃。 requestAnimationFrame将阻止这种情况发生。

将运行函数更改为

function run() {
    newframe();
    ctx.putImageData(ripple, 0, 0);
    requestAnimationFrame(run);
}

在代码底部删除setInterval(run,delay);并添加

requestAnimationFrame(run);

并将第二个setInterval更改为

var drops = function() {
    disturb(rnd() * width, rnd() * height);
    setTimeout(drops,700)
};
drops();

切勿使用setInterval,特别是对于这种类型的CPU密集型代码。您将导致许多计算机使页面崩溃。我们设置超时或请求动画帧代替。

同时删除创建纹理的 with 语句。