19

In my close function I want to do all my DOM clean-up stuff after css transitions have finished running. But there might not be any transitions running/might be multi-stage ones - (maintaining the stylesheets is out of my hands).

How would I go about writing a function something like the following

function close () {
  myEl.removeClass('open');
  if (animation is running/about to be run) {
    // wait for transition to end, then recursively check to see if another 
    // one has started, wait for that ...
    // then
    cleanUpDOM(); 
  } else {
    cleanUpDOM(); 
  }
}

My thoughts so far are to wrap the initial check in a timeout/requestAnimationFrame in order to give the animation a chance to start then checking to see if it's running. Unfortunately, without a transitionstart event I have no idea how to check if a transition has begun.

edit Answers recommending jquery are irrelevant as jquery animations are javascript animations, not CSS transitions

wheresrhys
  • 22,558
  • 19
  • 94
  • 162
  • if you know what property is being animated can you determine if the animation is in progress by comparing the elements current property value with what it's css style value (aka target) is? – milks Dec 10 '13 at 17:06
  • @milks In general I won't know what's being transitioned, or even if anything is being transitioned at all. (Worst case scenario is even trickier in fact - I might not even know if the transition is on the outer element e.g. in the overlay the inner content area may shrink to a point though the outer overlay just stays black... but this scenario isn't so important for now) – wheresrhys Dec 10 '13 at 17:16
  • I think this is a duplicate of http://stackoverflow.com/questions/2087510/callback-on-css-transition – Rob M. Dec 10 '13 at 17:22
  • @RobM. No, it isn't. This question is about an event at the start of the transition (which doesn't exists), that question is about an event at the end. – Carlo Cannas Dec 10 '13 at 18:19
  • This may help a bit: http://blog.teamtreehouse.com/using-jquery-to-detect-when-css3-animations-and-transitions-end – koosa Dec 10 '13 at 19:17
  • @milks - on reflection, your idea might be able to be adapted into something. I'm thinking read `transition-property` from `getComputedStyle`, then get the value of all the properties listed there, then apply the class, then `getComputedStyle` again and read all the properties to see if they've changed (I'm not even sure if the spec says they should change immediately if there's a transition defined, or before there's a repaint?) I also need to consider a fallback for when a bug means the event doesn't fire. Taking vendor prefixes into account it's gonna be a monster function. – wheresrhys Dec 11 '13 at 09:23
  • Check this. http://stackoverflow.com/questions/1397251/event-detect-when-css-property-changed-using-jquery – Jeyarathnem Jeyachanthuru Apr 18 '14 at 08:33

9 Answers9

1

About transitionStart and transitionEnd events:

The transition can't starts from nowhere. Usually transition starts after some event, where you change the state of DOM element by changing styles by class or something else. So you know when transition starts because you start it in your code.

During the transition user I/O don't blocks, so transition is asynchronous and then transition will end you don't know right. So you needs transitionEnd event to do something then transition has finished in javascript.

About transitionEnd event: Just look the jsfiddle

Alex
  • 11,115
  • 12
  • 51
  • 64
  • 5
    I don't start it explicitly in my `js` code. I add a class in `js`, which applies some styles in the `css` which *may* involve a transition so my `js` doesn't know if a transition is starting – wheresrhys Dec 11 '13 at 09:20
  • You can check transition support(with prefixes or not) by [Modernizr](http://modernizr.com/). If transition is supported you can check style object for `transitionDelay`, `transitionDuration` and `transitionProperty` and get there values to know will transition start or not. – Alex Dec 11 '13 at 09:29
  • 1
    You can't know for sure when transition starts because it's an async operation the browsers executes you don't know when. Take a look: https://stackoverflow.com/questions/27275872/race-condition-with-css-transitions-and-transitionend-event-cannot-find-a-solu – demian85 Dec 04 '14 at 14:31
1

Here's my solution so far - a bit hacky and only works when which element might transition is known, and doesn't work with transition-property: all... but it's a promising start

function toCamelStyleProp (str) {
    return str.replace(/(?:\-)([a-z])/gi, function ($0, $1) {
        return $1.toUpperCase();
    });
}

function toHyphenatedStyleProp (str) {
    return str.replace(/([A-Z])/g, function (str,m1) {
        return '-' + m1.toLowerCase();
    }).replace(/^ms-/,'-ms-');
}

function getPrefixedStyleProp (prop) {
    prop = toCamelStyleProp(prop);
    prop = Modernizr.prefixed(prop);
    return toHyphenatedStyleProp(prop);
}

function getStyleProperty (el, prop) {
    return getComputedStyle(el,null).getPropertyValue(getPrefixedStyleProp(prop));
}

function doAfterTransition ($wrapper, cssClass, mode, $transitioningEl, callback) {
    $transitioningEl = $transitioningEl || $wrapper;

    var transitioningEl = $transitioningEl[0],
        duration = +getStyleProperty(transitioningEl, 'transition-duration').replace(/[^\.\d]/g, ''),
        transitioners = getStyleProperty(transitioningEl, 'transition-property').split(' '),
        initialState = [],
        changedState = [],
        i,
        callbackHasRun = false,

        //makes sure callback doesn't get called twice by accident
        singletonCallback = function () {
            if (!callbackHasRun) {
                callbackHasRun = true;
                callback();
            }
        };

    // if no transition defined just call the callback
    if (duration === 0) {
        $wrapper[mode + 'Class'](cssClass);
        callback();
        return;
    }

    for (i = transitioners.length - 1;i>=0;i--) {
        initialState.unshift(getStyleProperty(transitioningEl, transitioners[i]));
    }

    $wrapper[mode + 'Class'](cssClass);

    setTimeout(function () {
        for (i = transitioners.length - 1;i>=0;i--) {
            changedState.unshift(getStyleProperty(transitioningEl, transitioners[i]));
        }

        for (i = transitioners.length - 1;i>=0;i--) {
            if (changedState[i] !== initialState[i]) {
                $transitioningEl.transitionEnd(singletonCallback);

                // failsafe in case the transitionEnd event doesn't fire
                setTimeout(singletonCallback, duration * 1000);
                return;
            }
        }
        singletonCallback();
    }, 20);
}
wheresrhys
  • 22,558
  • 19
  • 94
  • 162
1

There is no way (that I know of) to detect if a transition is currently working in the background without knowing the element that is being transitioned.

However, if you can move away from transition to key frame animations, then you'd have the so needed event - animationStart and animationEnd and then it will be easy to figure out if there are running animations.

PiniH
  • 1,901
  • 13
  • 20
0

If you're planning to make css transition, you can check out jQuery Transit Plugin http://ricostacruz.com/jquery.transit/

Very powerfull and useful, you can get transform x value with. css('x') for example.

Hugo Gresse
  • 17,195
  • 9
  • 77
  • 119
0

Have you tried the JQuery pseudo ":animated"?

if( $(elem).is(':animated') ) {...}

See More http://api.jquery.com/animated-selector/

user3674465
  • 27
  • 1
  • 4
  • 2
    That will only work with jquery's javascript animation https://github.com/jquery/jquery/blob/master/src/effects/animatedSelector.js - see how it expects there to be a timer in jquery – wheresrhys Jun 02 '14 at 15:02
0

Here is a function that waits for the page Html to become stable. i.e. when all animations are finished. In the example below it waits for the Html to be unchanging for 200 milliseconds and a maximum timeout of 2 seconds.

Call the function with ...

waitUntilHtmlStable(yourCallback, 200, 2000);

The function ...

waitUntilHtmlStable = function (callback, unchangedDuration, timeout, unchangedElapsed, html) {
    var sleep = 50;
    window.setTimeout(function () {
        var newHtml = document.documentElement.innerHTML;
        if (html != newHtml) unchangedElapsed = 0;
        if (unchangedElapsed < unchangedDuration && timeout > 0)
            waitUntilHtmlStable(callback, unchangedDuration, timeout - interval, unchangedElapsed + interval, newHtml);
        else
            callback();
    }, sleep);
};

In my case I wanted to be sure new elements where present. If you want to track animation movement then change the document.documentElement.innerHTML to

JSON.stringify(Array.prototype.slice.call(document.documentElement.getElementsByTagName("*"), 0)
.map(function(e) {
        var x = e;
        var r = x.getBoundingClientRect();
        while (r.width == 0 || r.height == 0) {
            x = x.parentNode;
            r = x.getBoundingClientRect();
        }
        return r;
    }));
Jay Byford-Rew
  • 5,736
  • 1
  • 35
  • 36
0

There is an unprefixed transitionstart event in IE10+. It is even cancelable.

https://msdn.microsoft.com/library/dn632683%28v=vs.85%29.aspx

Bundyo
  • 2,195
  • 13
  • 13
-2

On animation.css i found this.

You can also detect when an animation ends:

$('#yourElement').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', doSomething);

read full doc here

Amit
  • 1,841
  • 1
  • 19
  • 36
-2

you could use Jquery which would be much easier for example you could use .animate like this

(function(){
            var box = $('div.box')
            $('button').on('click', function(){
                box.animate({ 'font-size' : '40px'})
                .animate({'color': 'red'});
            })
        })();

or simply do a callback function

Eduardo Hernandez
  • 469
  • 1
  • 5
  • 11