Add tweens to be completed sequentially when triggered from rapid ui events

127 views Asked by At

I have a three.js app setup and I'm trying to add a nice transition between two models.

It should quickly scale down to zero, then scale the next model up from zero to its correct size.

I'm using tween.js and if the ui event happens again quickly while the previous is still animating then they end up trying to run together and interleaving.

I just want it to add it to the queue as my tween has side effects in an oncomplete handler.

setActiveBoxTypeByName(name) {
    let rnd = Math.random();
    console.log("starting setActiveBoxTypeByName", rnd);
    let boxToHide = this.scene.getObjectByName(Config.activeBoxName);
    let boxToDisplay = this.scene.getObjectByName(name);

    let originalScaleHide = new THREE.Vector3(0, 0, 0).copy(boxToHide.scale);
    let originalScaleShow = new THREE.Vector3(0, 0, 0).copy(boxToDisplay.scale);

    let tweenScaleToZero = new TWEEN.Tween(boxToHide.scale)
    .to({x: 0, y:0, z: 0}, 150)
    .easing(TWEEN.Easing.Quadratic.In)
    .onComplete(() => {
        boxToHide.visible = false;
        boxToHide.scale.copy(originalScaleHide);
        boxToDisplay.scale.copy(new THREE.Vector3(0,0,0));
        boxToDisplay.visible = true;
        console.log("oncomplete setActiveBoxTypeByName", rnd);
    });

    let tweenScaleUpFromZero = new TWEEN.Tween(boxToDisplay.scale)
    .to(originalScaleShow, 150)
    .easing(TWEEN.Easing.Quadratic.InOut)
    .onComplete(() => {
        Config.activeBoxName = name;
        console.log("oncomplete setActiveBoxTypeByName", rnd);
    });

    tweenScaleToZero.chain(tweenScaleUpFromZero).start();
}

The problem is that it is attached to a radio button list and if I flip between the two quickly then it gets into a mess.

This is because two separate sets of tweens can end up playing at the same time. I put some console.log()s in there to prove this is what is happening:

    starting setActiveBoxTypeByName 0.8409190378614766
    oncomplete setActiveBoxTypeByName 0.8409190378614766
    oncomplete setActiveBoxTypeByName 0.8409190378614766
    starting setActiveBoxTypeByName 0.5498841071087592
    oncomplete setActiveBoxTypeByName 0.5498841071087592
    starting setActiveBoxTypeByName 0.16717439330596795 // here
    oncomplete setActiveBoxTypeByName 0.5498841071087592 // here
    oncomplete setActiveBoxTypeByName 0.16717439330596795
    oncomplete setActiveBoxTypeByName 0.16717439330596795

I can't think of a way to write it without the side effects as it needs to do the animation and then set visible to false, and I can't see a way to add to the existing queue.

Is there a trick I am missing?

1

There are 1 answers

0
Fabrizio On

First of all, I use TweenLite but I think you have the same methods in Tween.

What I think is that you can take count of how many times you click on the radio to take count on how many animations you have to do. Then you can check if the first animation is still in progress and the second one has to start in the first one's "oncomplete" method.

Here's an example:

var iterations = 0;
var tweenScaleToZero, tweenScaleUpFromZero;

$('#yourRadioButton').click( function() { iterations++; }

setActiveBoxTypeByName(name) {
    let rnd = Math.random();
    console.log("starting setActiveBoxTypeByName", rnd);
    let boxToHide = this.scene.getObjectByName(Config.activeBoxName);
    let boxToDisplay = this.scene.getObjectByName(name);

    let originalScaleHide = new THREE.Vector3(0, 0, 0).copy(boxToHide.scale);
    let originalScaleShow = new THREE.Vector3(0, 0, 0).copy(boxToDisplay.scale);

    if( ( !tweenScaleToZero.isActive() || tweenScaleToZero === undefined ) && 
        ( !tweenScaleUpFromZero.isActive() || tweenScaleUpFromZero === undefined) ) { 

        tweenScaleToZero = new TWEEN.Tween(boxToHide.scale) 
            .to({x: 0, y:0, z: 0}, 150)
            .easing(TWEEN.Easing.Quadratic.In)
            .onComplete(() => {
            boxToHide.visible = false;
            boxToHide.scale.copy(originalScaleHide);
            boxToDisplay.scale.copy(new THREE.Vector3(0,0,0));
            boxToDisplay.visible = true;
            console.log("oncomplete setActiveBoxTypeByName", rnd);

            tweenScaleUpFromZero = new TWEEN.Tween(boxToDisplay.scale)
                .to(originalScaleShow, 150)
                .easing(TWEEN.Easing.Quadratic.InOut)
                .onComplete(() => {
                    Config.activeBoxName = name;
                    console.log("oncomplete setActiveBoxTypeByName", rnd);
                });

        }).start();

        if( iterations > 0 ) iterations--;

    } else console.log( "Boxes are already animating." );

}