Change jQuery's animation duration during anim

2019-04-06 23:59发布

问题:

Is it possible to change the duration of a currently running jQuery animation between two different values?

I've tried to change the duration via direct assignment, but no success:

var timing = { duration: 4000 };
$(document).click(function (e) {
  timing.duration = 1000;
});

$('#foo').animate({top:200, left:200}, timing);

...and even, changing the fx.options.duration in step-method does not affect the running animation:

var state = false,
$(document).click(function (e) {
  state = true;
});

$('#foo').animate({top:200, left:200}, {
  duration: 4000,
  step: function(now, fx){
    if(state) fx.options.duration = 1000;
    console.log(fx.options.duration); // 1000
  }
});

Here's a fiddle to play around. Any ideas how this can be done?

回答1:

The duration is passed by value, not by reference. So animate does not store a reference to duration. Even if you update the options object (which is passed by reference) jQuery uses options.duration internally, which means it will be passed by value.

As a quick fix you could stop the animation and restart it with the new duration - adjusting for the part of the animation that is already over.

You'd need to consider how you want it to behave, for example when you speed up a 4 second animation to a 2 second animation after 3 seconds. In the code below the animation will be immediate. Thinking about it, that's probably not what you want since you probably really care about speed, not duration.

The code below is a rough sketch, I'm not sure if it's accurate, if it works when decreasing animation values or how it handles multiple animation values. You can also only change the duration once.

var state = false,
    duration = 8000;

$(document).click(function (e) {
    state = true;
    duration = 1000;
});
var animationCss = {top:200, left:200};
$('#foo').animate(animationCss, {
    duration: duration,
    step: function(now, fx){
        if(state) {
             $("#foo").stop();
             var percentageDone = (fx.now - fx.start) / (fx.end - fx.start) 
             var durationDone = fx.options.duration * percentageDone;
             var newDuration = duration - durationDone;
            if (newDuration < 0)
            {
                 newDuration = 0;   
            }
            $("#foo").animate(animationCss, { duration: newDuration})

        }
    }
});

http://fiddle.jshell.net/5cdwc/3/



回答2:

I'm probably a bit late for op but since I ended up here while trying to do the same thing, someone else may too.

On the start() callback jQuery pass the animation object as argument, so you just have to keep the reference somewhere and then you can change it on the step() callback.

Something like :

var animEnd = false; //this will be updated somewhere else 
var animObj;
$().animate({
        width: '100%'
    },
    {   
        start: function(animation){
            animObj = animation;
        },
        step: function(now, tween){
            if(!animEnd)
                //jquery set an interval of 13 but let's be safe
                animObj.duration += 30;

            //you can change the value of tween.pos aswell
        }
    }
);

Now the tricky part is that jQuery decide to stop or continue the animation before calling the step() callback. So the duration is set for the next tick and if it wasn't set big enough your animation may be stopped.
jQuery use setInterval() with a interval of 13 so theoretically step() should be called every 13ms but since nothing is guaranteed I think duration should be increased by 30ms as a minimum.
Personally I would even go for at least 100. Better have an animation that runs a bit too long (100ms is almost unnoticeable) rather than having it to stop too soon.