8
votes

I'm using a CSS3 step transition to animate a sprite from one state to another.

When I start one animation it shows the frames one at a time and has a nice transition effect (click "show/hide" link in the example). But when a second transition is triggered while the first one is still running the frame position get's lost and it looks like it scrolls to the other side instead of maintaining the frame-by-frame animation (click "trigger bug" in the example).

.tree {
    width: 26px; /* one frame */
    height: 31px; /* frame height */
    background-image: url("http://rolandschuetz.at/docs/tree-animated.png");
    background-repeat: no-repeat;
    background-position: -234px 0; /* last frame */
    transition: background-position .8s steps(10); /* this triggers the CSS3 step transition */
}
.tree-hidden {
    background-position: 26px 0; /* clear, before first frame */
}

Is there a way to force the animation to work correctly even when it aborted an older one?

PS: Please not try to "fix" by trigger-bug button which is only there for demo purposes. The real problem is triggered by fast user interaction which should have immediate feedback.

4
Did you press the "trigger bug" link? If so, in which browser? - Roland Schütz
yea did but since its your animation you have to explain whats ugly there so that i can find ugly part - sanoj lawrence
when i press tree size gets reduced to small and scroll from left to right - sanoj lawrence
Yes, and it shouldn't scroll. This should be a frame-by-frame grow animation like you can see when you press the show/hide link. - Roland Schütz
I've figured out what's going on here, I'll draw up a diagram and put it an answer below. - PavKR

4 Answers

2
votes

Updated code

Check if this helps you.

HTML

<div class="outer"> <!--Added this div-->
  <div class="tree"></div>        
</div>

CSS:

.outer {
    width: 26px;
    border: 1px solid grey;
}
.tree {
    width: 26px; /* one frame */
    height: 31px;

    background-image: url("http://rolandschuetz.at/docs/tree-animated.png");
    background-repeat: no-repeat;
    background-position: -234px 0; /* last frame */
    -webkit-transform: scale(1);
    margin: 0 auto;    
    /*Changed transition*/
    -webkit-transition: all .8s;
       -moz-transition: all .8s;
        -ms-transition: all .8s;
            transition: all .8s;
}
.tree-hidden {
    /* background-position: 26px 0; */ /* empty, before first frame */
    width: 0;
    -webkit-transform: scale(0);
}
2
votes

Here's the issue:

There are 10 frames, hence you use 10 steps for the animation. So basically, the CSS is stepping the tree 10 times for tree-active and 10 times for tree-not-active.

If you de-activate the tree at frame 8, it will reverse the animation direction as expected but it will step 10 times instead of 8. This makes it look like it is sliding but it's actually just stopping in the 10 interval positions for the 8 frames.

Does this make sense?

For this type of animation you'd be better off using a JS solution where it knows the current frame position and calculates the steps needed correctly.

I hope this helps!

2
votes

As ThePav says, there is no way to make CSS know in wich state it is. So, it will aplly always a steps(10) function, even when starting from the middle of the previous transition, and with a background-position that would need less steps.

The easiest way to solve this (not easy, but the easiest) is to set a parallel z-index transition, set the same way as the background-position. If you can give them any z-index, then the z-index would go from 0 to 10.

.tree {
    background-position: -234px 0; 
    z-index: 0;
    transition: all .8s steps(10); // will apply both to bkg-position and z-index
}
.tree-hidden {
    background-position: 26px 0; 
    z-index: 10;
}

Then, the z-index property serves as an indicator of a ongoing transition,and you can set the steps function accordingly, in the scripting to change the class.

That is, get value of z-index, and set that to transitionTimingFunction: 'steps (' + zindexval + ')'

(You could do this also with the background-position property, but it is way harder.)

The code would be

function change () {
    var tree = $('.tree').eq(0);
    if (tree.hasClass('tree-hidden')) {
        var where = tree.css("zIndex");
        if (where != 10) {
            tree.css({transitionTimingFunction: 'steps(' + where + ')'})
        } else {
            tree.css({transitionTimingFunction: ''})
        }
        tree.removeClass('tree-hidden');
    } else {
        tree.css({transitionTimingFunction: ''})
        tree.addClass('tree-hidden');
    }

}

demo

0
votes

Use transitionend for waiting to remove 'tree-hidden' class. Else you could't solve by another way.

$('.show-hide').click(function() {
    !$('.tree').hasClass('tree-hidden') &&
    $('.tree').addClass('tree-hidden').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
        $('.tree').removeClass('tree-hidden')
    });
});

UPDATE

 $('.show-hide').click(function(evt) {
    //JUST CHECK ALREADY HAS HIDDEN CLASS
    if($('.tree').hasClass('tree-hidden'))
     //you can event prevent defaul if there is no another work
     evt.preventDefault();
     return false;
    $('.tree').addClass('tree-hidden').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
        $('.tree').removeClass('tree-hidden')
    });
});     

By this way, transaction would not break until animation done, regardless of clicking more time.

css transaction fallback

If you need fallback you can use code below

function supportsTransitions() {
   var b = document.body || document.documentElement,
    s = b.style,
    p = 'transition';

    if (typeof s[p] == 'string') { return true; }

    // Tests for vendor specific prop
    var v = ['Moz', 'webkit', 'Webkit', 'Khtml', 'O', 'ms'];
    p = p.charAt(0).toUpperCase() + p.substr(1);

    for (var i=0; i<v.length; i++) {
        if (typeof s[v[i] + p] == 'string') { return true; }
    }

    return false;
}