Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smooth jQuery-like animation in GWT

I'm playing with GWT's Animation class as an exercise. My goal is not to create yet another widget/animation library for GWT. Also, I'm well aware that there are many libraries that do this. What I'm trying to do is learn how this can be done, not how to use some library. This is for educational purposes only....

With that said, I've implemented a simple class that performs 4 different animations on some widget: fade in, fade out, slide in, and slide out (following jQuery's animation model). See the code below for how I've implemented it.

LIVE DEMO: http://www.rodrigo-silveira.com/gwt-custom-animation/

My question: How can I smoothly [and successfully] stop an animation as soon as another one is triggered, and continue the current animation where the old one left off?

For example, if the slideIn() is half way through when slideOut() is called, how can I start sliding out from the widget's natural height of 50%? I tried keeping a member variable that always keeps track of the current progress so I could use that in a new animation, but can't seem to get it right. Any ideas?

import com.google.gwt.animation.client.Animation;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Element;

public class RokkoAnim extends Animation {

    private Element element;
    private int currentOp;
    private final static int FADE_OUT = 0;
    private final static int FADE_IN = 1;
    private final static int SLIDE_IN = 2;
    private final static int SLIDE_OUT = 3;


    public RokkoAnim(Element element) {
        this.element = element;
    }

    public void fadeOut(int durationMilli) {
        cancel();
        currentOp = RokkoAnim.FADE_OUT;
        run(durationMilli);
    }

    public void fadeIn(int durationMilli) {
        cancel();
        currentOp = RokkoAnim.FADE_IN;
        run(durationMilli);
    }

    public void slideIn(int durationMilli) {
        cancel();
        currentOp = RokkoAnim.SLIDE_IN;
        run(durationMilli);
    }

    public void slideOut(int durationMilli) {
        cancel();
        currentOp = RokkoAnim.SLIDE_OUT;
        run(durationMilli);
    }

    @Override
    protected void onUpdate(double progress) {
        switch (currentOp) {
        case RokkoAnim.FADE_IN:
            doFadeIn(progress);
            break;
        case RokkoAnim.FADE_OUT:
            doFadeOut(progress);
            break;
        case RokkoAnim.SLIDE_IN:
            doSlideIn(progress);
            break;
        case RokkoAnim.SLIDE_OUT:
            doSlideOut(progress);
            break;
        }
    }

    private void doFadeOut(double progress) {
        element.getStyle().setOpacity(1.0d - progress);
    }

    private void doFadeIn(double progress) {
        element.getStyle().setOpacity(progress);
    }

    private void doSlideIn(double progress) {
        double height = element.getScrollHeight();
        element.getStyle().setHeight(height * (1.0d - progress), Unit.PX);
    }

    private void doSlideOut(double progress) {
            // Hard coded value. How can I find out what
            // the element's max natural height is if it's 
            // currently set to height: 0 ?
        element.getStyle().setHeight(200 * progress, Unit.PX);
    }
}

Usage

// 1. Get some widget
FlowPanel div = new FlowPanel();

// 2. Instantiate the animation, passing the widget
RokkoAnim anim = new RokkoAnim(div.getElement());

// 3. Perform the animation

// >> inside mouseover event of some widget:
anim.fadeIn(1500);

// >> inside mouseout event of some widget:
anim.fadeOut(1500);
like image 763
rodrigo-silveira Avatar asked Jan 10 '13 04:01

rodrigo-silveira


2 Answers

You need to update the opacity based on the current value (equivalent to += or -= assignments):

private void doFadeOut(double progress) {
    double opacity = element.getStyle().getOpacity();
    element.getStyle().setOpacity(opacity - 1.0d - progress);
}

private void doFadeIn(double progress) {
    double opacity = element.getStyle().getOpacity();
    element.getStyle().setOpacity(opacity + progress);
}
like image 69
Shedolamack Avatar answered Sep 23 '22 23:09

Shedolamack


  1. Add a instance method bool canceled to your annimation class
  2. Only run the annimation if not canceled.
  3. Add a method cancelAnnimation and call this method everytime when the new annimation starts.

    private bool canceled = false;
    
    public void cancelAnnimation() {
        canceled = true;
    }
    
    private void doFadeOut(double progress) {
        if(!canceled) {
           element.getStyle().setOpacity(1.0d - progress);
        }
    }
    
    ... etc.
    

So thats the way you can stop the animation. But now, you need to start the new animation from the current point. So the best was to modify the class again.

  1. add two instance variables currentProgress and offset (double)
  2. add an double offset to the constructor
  3. return currentProgress when cancel the annimation.
  4. start the new animation with the offset from the first animation.

    private bool canceled = false;
    private double currentProgress = 0;
    private double offset = 0;
    
    public RokkoAnim(Element element, double offset) {
        this.element = element;
        this.offset = offset;
    }
    
    public double cancelAnimation() {
        canceled = true;
        return currentProgress;
    }
    
    private void doFadeOut(double progress) {
    
        if(!canceled && ((progress + offset) <= 1)) {
           element.getStyle().setOpacity(1.0d - (progress+offset));
           currentProgress = progress+offset;
        }
    }
    
    ... etc.
    
    
    double offset = anim1.cancelAnimation();
    RokkoAnim anim2 = new RokkoAnim(div.getElement(), offset);
    
like image 43
Sam Avatar answered Sep 21 '22 23:09

Sam