its2easy/animate-images

playTo (and playFrames) being ignored when it switches from fastPreview to full

laurensV opened this issue · 4 comments

when you are in the middle of a playTo animation (e.g. playTo(100)), while using the fastPreview mode, and at some point during this animation it finished the full preload and switches from fast to full, it ignores the playTo and just switches to regular play(), causing it to also continue after frame 100.

if ( wasAnimating ) plugin.play();

const wasAnimating = plugin.isAnimating();
const matchFrame = this._settings?.fastPreview.matchFrame;
plugin.setFrame( matchFrame ? matchFrame(this._data.currentFrame) : 1 );
if ( wasAnimating ) plugin.play();

There you see that by first calling setFrame (causing a call to stop(), which resets _framesLeftToPlay) and then play() again. Can't we just simply change it to this, so that it only loses the playTo animation when you dont use the matchFrame option?

const matchFrame = this._settings?.fastPreview.matchFrame;
if (!matchFrame) {
    const wasAnimating = plugin.isAnimating();
    plugin.setFrame(1);
    if (wasAnimating) plugin.play();
}

If so, happy to make a PR for this :)

@laurensV In your version you don't call matchFrame even if it's defined. So you should call setFrame to use the result of matchFrame, which will cause _framesLeftToPlay to be reset anyway. That means we need a way to store _framesLeftToPlay value before setFrame and then restore it.
If you wanna make a PR you can create new function in AnimateImages that will get of set _framesLeftToPlay value and then pass it to Animation constructor. Something like this:

    #manageFramesLeftToPlay(value){
        // 'arguments' to handle possible 'undefined' value
        if (arguments.length) this.#animation._framesLeftToPlay = value;
        else return this.#animation._framesLeftToPlay;
    }

But do we really need to continue playTo and playFrames after full load? With play(), we simply continue the infinite animation, saving the progress percentage after full load. With playTo and playFrames we have a specific number of frames to play, but the total frames numer changes.
For example when fastPreload is 30 images, full sequence is 150. playTo(15) means animation to the middle. If full preload finishes when animation at frame 9, the rest is 6 frames. But if we play 6 remaining frames, end position will be at 15/150, which is far from the middle.
So maybe it's better to stop the animation if _framesLeftToPlay is set and let developers start a new one (according to the updated quantity) in onFastPreloadFinished callback?

My scenario is a bit complicated (will happily share the end-result with you once its live) and never just plays the entire animation. Based on certain steps in the process it will play a certain section of the animation back and forth and in some scenarios drag is enabled and in other scenarios drag is disabled etc. Thats why it would be helpful if it would just keep playing to playTo and playFrames in my scenario. For me the fastPreload has the same amount of frames as the full sequence (just with lower quality images), so that's why I don't need the matchFrame function, but in your scenario you would need that of course.

For example when fastPreload is 30 images, full sequence is 150. playTo(15) means animation to the middle. If full preload finishes when animation at frame 9, the rest is 6 frames. But if we play 6 remaining frames, end position will be at 15/150, which is far from the middle.

This could be fixed when we automatically reinstantiate the playTo(15) animation to the new matching frame (e.g. playTo(matchFrame(15)) )

So maybe it's better to stop the animation if _framesLeftToPlay is set and let developers start a new one (according to the updated quantity) in onFastPreloadFinished callback?

I agree, for now this is the most simple solution (could even be achieved in its current form, without having to stop the animation), but I think you ment in the onPreloadFinished?

but I think you ment in the onPreloadFinished

Yes, I mean someting like this:

let currentTargetFrame;
let instance = new AnimateImages(element, {
        images: imagesArray,
        ...
       onFastPreloadFinished: (plugin) => {
            startActions();
        },
        onPreloadFinished: (plugin) => {
            if (currentTargetFrame) plugin.playTo(currentTargetFrame);
        },
        ...
}

function startActions(){
     if (condition1) { // mb some scroll events
        currentTargetFrame = 15;
        instance.playTo(15);
     }
     if (condition2) {
        currentTargetFrame = 45;
        instance.playTo(45);
     }
}

This could be fixed when we automatically reinstantiate the playTo(15) animation to the new matching frame (e.g. playTo(matchFrame(15)) )

I can't because playTo is being converted to playFrames, and 'playFrames' is being converted to _framesLeftToPlay and direction. Target frame is not calculated internally and matchFrame formula cannot be applied to number of frames. That's why I think it's better to call playTo in load callback like in example above.

Now I see the login like this:
animation is not active => setFrame to the result of matchFrame or 1
animation is active, _framesLeftToPlay is undefined => setFrame to the result of matchFrame or 1, then play
animation is active, _framesLeftToPlay is number => stop animation, setFrame to the result of matchFrame or 1

If the callback version is not suitable, then I can probably add a new option fastPreview.continueAfter that changes the logic to this:
animation is not active => setFrame to the result of matchFrame or 1
animation is active, _framesLeftToPlay is undefined, continue is true => setFrame to the result of matchFrame or 1, then play
animation is active, _framesLeftToPlay is undefined, continue is false => setFrame to the result of matchFrame or 1, then stop
animation is active, _framesLeftToPlay is number, continue is false => stop animation, setFrame to the result of matchFrame or 1
animation is active, _framesLeftToPlay is number, continue is true => directly change current frame to the result of matchFrame or 1, without stopping the animation

While the continueAfter flow would be nice, for me the callback version is suitable and I am not sure how often people come across this problem, so I think we can close this issue for now, thanks for all your comments!