Skip to main content
 首页 » 编程设计

JQuery animate之有什么方法可以完整触发 "stall"吗

2026年07月28日11mayingbao

假设我有一个具有定义的宽度和高度的视口(viewport)。我克隆了右侧 View 之外的一个元素,然后随机将其飞过视口(viewport) y位置代码如下:

    ... 
    .css({ 
        'left': BASE_NODE_INIT_LEFT_PX, 
        'top' : rand(BASE_NODE_INIT_TOP_MIN_PX, BASE_NODE_INIT_TOP_MAX_PX) + 'px', 
        'width': _width + 'px', 
        'height': _height + 'px', 
    }) 
    .animate({ 
        left: BASE_NODE_ANIMATE_DISTANCE_X_PX 
    } 
    ... 

足够简单的动画。这是有趣的部分:在每一步中,我都想对这个元素应用一些物理原理。

类似这样的:

        ... 
        step:function(currentStep){ 
            if($(this).data('animating-wind') !== true) 
            { 
                $(this).data('animating-wind',true); 
                var wind = (rand(1,2) == 2) ? '+=' + rand(1, 100) + 'px' : '-=' + rand(1, 100) + 'px'; 
                $(this).animate({ 
                    'top': wind, 
                    'text-indent': wind, 
                },{ 
                    duration: 500, 
                    complete: function(){ 
                        $(this).data('animating-wind',false); 
                    }, 
                    queue: false 
                }); 
            } 
        } 
        ... 

基本上发生的事情不是从右到左直线飞行,而是减速、加速、随机上升和下降,正如我的意图。

我面临的问题是,有时“风”足够强,以至于当步数达到计算的总步数时,元素在视口(viewport)上仍然可见,并且它会消失(这发生在我的complete:function(){ ...$(this).remove(); ...}

显然,JQuery 认为动画已经完成,因为它计算了步骤并说“我正在为这个对象设置动画,在这么多毫秒内经过这么多步骤 - 它就在那里”。但是风动画在动画队列之外,所以这个动画过程并不明智。

我想要的是开始动画并保持动画直到元素以任何方向退出视口(viewport) - 一旦它不再可见,无论是从其初始飞行路径还是从屏幕上吹来的风,我将检测到它并触发 onComplete打回来。我唯一能想到的就是将此动画放在函数内并放在 complete:function(){} 中对自身进行递归调用,然后在 step:function(){} 内我会进行“在视口(viewport)中可见”检查并调用 stop(true,false)如果true 。这似乎是一个非常昂贵的检查 - 在 400-1200 毫秒内运行该计算 100 多次可能会使动画不稳定,因此我正在寻找更优雅的解决方案。

TL;博士: 由于设置了其他动画而尚未到达目的地,我如何保持动画效果?

更新:我申请了@mu is too short's idea并想出了这个:

/**  
 * Create a new clone of the master div for user interaction. Position it  
 * randomly off the canvas, and animate it across at a random speed. 
 */ 
 
function spawn_target() { 
    spawn_timeout = setTimeout(function() { 
        var bonus = (rand(1, BONUS_ODDS) == BONUS_ODDS) ? ' ' + BONUS_CLASS : ''; 
        var _img_src = (bonus.length > 0) ? BONUS_NODE_IMG_SRC : BASE_NODE_IMG_SRC; 
        var _width = $('#' + BASE_NODE_ID).width(); 
            _width = Math.floor(rand(_width / 3, _width)); 
        var _height = _width; 
        var _framerate = 10 - Math.floor(_height/10); 
        var _clone = $('#' + BASE_NODE_ID).clone().addClass(CLONE_CLASS + bonus).attr('id', CLONE_ID).appendTo('#' + CANVAS_ID).css({ 
            'left': BASE_NODE_INIT_LEFT_PX, 
            'top': rand(BASE_NODE_INIT_TOP_MIN_PX, BASE_NODE_INIT_TOP_MAX_PX) + 'px', 
            'width': _width + 'px', 
            'height': _height + 'px', 
        }) 
 
        $(_clone).children('img').attr('src', _img_src); 
 
        /** 
         * Handle the actual flight across the viewport  
         * @param object _this The clone we are animating manually 
         * @return object pointer This contains the interval so we can clear it later 
        */ 
        function fly(_this) { 
 
            var animating_wind = false; 
            var pointer = { 
                timer_id: null 
            }; 
            pointer.timer_id = setInterval(function() { 
 
                // Get rid of the node if it is no longer visible in the viewport 
                if (!$(_this).is(':onviewport')) { 
                    clearInterval(pointer.timer_id); 
                    $(_this).remove(); 
                    adj_game_data(GAME_DATA_LIVES_ID, -1); 
                    check_lives(); 
                    spawn_target(); 
                } 
 
                // Move node along with slight realism 
                var distance = rand(FLY_DISTANCE_MIN, FLY_DISTANCE_MAX); 
                $(_this).css('left', '-=' + distance + 'px'); 
 
                // Only trigger the wind animation when it is idle 
                if (animating_wind !== true) { 
                    animating_wind = true; 
 
                    // Setup the wind physics 
                    var _wind_power = rand(WIND_POWER_MIN, WIND_POWER_MAX); 
                    var _wind_dir = (rand(1, 2) == 2) ? '+=' : '-='; 
 
                    // Override wind direction to keep node inside viewport 
                    if(($(_this).offset().top + $(_this).height() + _wind_power) > CANVAS_HEIGHT) 
                    { 
                        _wind_dir = '-='; 
                    } 
                    if(($(_this).offset().top - _wind_power) < CANVAS_TOP_BUFFER_PX) 
                    { 
                         _wind_dir = '+=';    
                    } 
 
                    var _wind = _wind_dir + _wind_power + 'px';   
 
                    // Apply the wind physics and don't let the node break the top or bottom 
                    // boundaries of the viewport 
                    $(_this).animate({ 
                        'top': _wind 
                    }, { 
                        duration: WIND_ANIMATION_DURATION, 
                        complete: function() { 
                            animating_wind = false; 
                        }, 
                        queue: false 
                    }); 
                } 
            }, _framerate); 
 
            return pointer; 
        } 
        $(_clone).data('interval', fly(_clone)); 
 
    }, rand(SPAWN_TARGET_TIMEOUT_MIN, SPAWN_TARGET_TIMEOUT_MAX)); 
} 

这是我到目前为止所拥有的:http://jsfiddle.net/AlienWebguy/DmtQ7/embedded/result/

我喜欢这个想法,虽然看起来 JQuery 应该有一些内置的东西,这正是我所希望的。现在,而不是 $(obj).stop()我必须clearInterval($(obj).data('interval').timer_id); 。 *抓伤头...

请您参考如下方法:

这就是我认为你应该做的(伪 JavaScript):

function animate_it($thing) { 
    var wind     = false; 
    var timer_id = setInterval(function() { 
        // Compute the movement for this step. 
        // Then factor in the wind effect and update the `wind` variable. 
        // Then apply the movement to `$thing`. 
        if(it_is_off_the_page($thing)) 
            clearInterval(timer_id); 
    }, n); 
} 

如果内部计算中有任何内容需要反复计算,请在 animate_it 级别预先计算它,并让闭包关闭它。您可能想稍微玩一下它来弄清楚 n 应该是什么。

只需手动制作动画,这样您就不会与 jQuery 的 animate 函数发生冲突,并在知道动画完成时停止动画。这还允许您几乎免费地包含风的方向和强度(以便您的动画对象可以向后移动)。

您可能希望将 timer_id 返回给外部世界,以便您可以从外部停止整个事件。在这种情况下,您需要用一个元素对象来伪造一个指针:

function animate_it($thing) { 
    var wind    = false; 
    var pointer = { timer_id: null }; 
    pointer.timer_id = setInterval(function() { 
        // Compute the movement for this step. 
        // Then factor in the wind effect and update the `wind` variable. 
        // Then apply the movement to `$thing`. 
        if(it_is_off_the_page($thing)) { 
            clearInterval(pointer.timer_id); 
            pointer.timer_id = null; 
        } 
    }, n); 
    return pointer; 
} 
 
var timer = animate_it($thing); 
 
// And then later, to shut it down... 
if(timer.timer_id) 
    clearInterval(timer.timer_id); 

如果您有很多动画对象,您可能希望在 animate_it 上执行“it is gone”回调,这样您就可以保持外部计时器列表干净且无瑕疵。

<小时 />

尝试使用 animate 执行此操作的问题是 animate 在开始时计算步骤数,然后您可以更改它。您需要的步骤数量不定,但 animate 并不关心(它甚至比蜜獾还关心)。