开发者

YUI: Stop event handlers from triggering while an animation is running

So I'm using YUI to add some animations in my application triggered when a user clicks certain elements. However, I'm running into a common problem that is easily fixed with some poor coding, but I'm looking for a more elegant solution that's less error-prone.

Often, when the user clicks something, a DOM element is animated (using Y.Anim) and I subscribe to that animation's 'end' event to remove the el开发者_StackOverflow社区ement from the document after its animation has completed. Pretty standard stuff.

However, problems arise when the user decides to spam-click the element that triggers this event. If the element is going to be removed from the DOM when the animation ends, and the user triggers an event handler that fires off ANOTHER animation on the same element, this 2nd animation will eventually cause YUI to spit really nasty errors because the node it was animating on suddenly disappeared from the document. The quickest solution I've found for this is to just set some module/class-level boolean state like 'this.postAnimating' or something, and inside the event handler that triggers the animation, check if this is set to true, and if so, don't do anything. In the 'end' handler for the animation, set this state to false.

This solution is really, really not ideal for many reasons. Another possible solution is to detach the event handler for duration of the animation and re-attach it once the animation is complete. This is definitely a little better, but I still don't like having to do extra bookkeeping that I could easily forget to do if forgetting to do so leads to incomprehensible YUI errors.

What's an elegant and robust way to solve this problem without mucking up a multi-thousand-line Javascript file with bits and pieces of state?

Here's some example code describing the issue and my solution to it.

var popupShowing = false,
    someElement  = Y.one('...');

someElement.on("click", showPopUp)

var showPopup = function(e) {
    if(!popupShowing) {
      popupShowing = true;
      var a = new Y.Anim({
          node: someElement,
          duration: 0.2,
          ...
      });

      a.on('end', function() {
          someElement.remove(true);
          popupShowing = false;
      });

      a.run();
    }
}

So if the user clicks "someElement" many times, only one animation will fire. If I didn't use popupShowing as a guard, many animations on the same node would be fired if the user clicked quickly enough, but the subsequent ones would error out because someElement was removed when the first completed.


Have a look at the Transition API. It's more concise, and may very well do what you want out of the box.

someElement.transition({ opacity: 0, duration: 0.2 }, function () { this.remove(); });
// OR
someElement.on('click', function () { this.hide(true, { duration: 0.2 }); });
// OR
someElement.on('click', someElement.hide);

Personally, I haven't used Anim since Transition was added in 3.2.0. Transition uses CSS3 where supported (with hardware acceleration), and falls back to a JS timer for older browsers. http://developer.yahoo.com/yui/3/examples/transition/transition-view.html


Edit: By popular demand, a YUI way:

myAnim.get('running')

tells you whether an animation is running. To use this you might have to restructure the way you call the event so the animation is in the right scope, for example:

YUI().use('anim', function (Y) {
   var someElement = Y.one('...');
   var a = new Y.Anim({
       node: someElement,
       duration: 0.2,
       ...
   });

   a.on('end', function() {
       someElement.remove(true);
   });

   someElement.on('click', function() {
       if (!a.get('running')) {
           a.run();
       }
   });
});

jsFiddle Example

Previously I had said: I personally like the way jQuery handles this. In jQuery, each element has a queue for animation functions. During animations an "in progress" sentinel is pushed to the front for the duration of the animation so anything that doesn't want to step on an animation peeks at the front of the queue for "in progress" and decides what to do from there, e.g. do nothing, get in line, preempt the current animation, etc.

I don't know enough about YUI to tell you how to implement this, but I find it to be a very elegant solution.


Quick & dirty solution to this particular issue?

Attach your handlers using .once() instead

someElement.once("click", showPopUp)

Also suitable if you need the handler re-attached later, just call that line again when the animation is done. You could also store your state information on the node itself using setData/getData but that is just a panacea to the real problem of state tracking.

Also, +1 to Luke's suggestion to use Transition for DOM property animation, it's grand.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜