Wednesday, December 16, 2009

Fully Executing jQuery Animations Without Queuing

Fully Executing jQuery Animations Without Queuing:

It is pretty common when using jQuery’s .animate() function that it is triggered by a mouseEnter or hover event. That’s all well and good, but it means that we need account for those events being triggered multiple times. If the element with the hover event attached is hovered over multiple times, that means the animation will be fired of several times, which is typically undesirable. The standard way to deal with this is using the .stop() function, like:

$(this).stop().animate({ width: '200px' });

This is definitely not the cure-all solution though. So let’s explore.

We already know that not using .stop() is problematic, because then the animations queue up and it is kind of awkward with multiple quick hovers. But not using .stop() is also kind of perfect in a way, because the mouseEnter animation and mouseLeave animation execute completely and sequentially. It is that smoothness that I am after here, only without the queuing.

Using .stop() prevents the queuing, but it also prevents the animations from completing a full cycle. Mouse off, the stop function fires, and stops the animation that triggered on mouseEnter and begins the animation resetting things.

The first thing I started messing with was in using .stop() prior to only one or the other of the animations, but that doesn’t help much. The .stop() function also has some parameters you can pass, the second of which dictates if the animation should be forced to complete first. If set to true, the animation will indeed finish, but it doesn’t do so smoothly, it jerks to it’s final state, which I find generally undesirable.

The solution lies in not beginning a new animation until the state of the element is not being animated. In that way, you don’t even need to worry about queuing, because only one animation can be running at a time anyway. There are a couple of ways to get there, but this I found the cleanest:

$('div').hover(function(){
    $(this).filter(':not(:animated)').animate({ width: '200px' });
}, function() {
    $(this).animate({ width: '100px' });
});

There was plenty of ideas on the journey here. Check out the demo below to see all the different options I went through, as well as an additional method that also works.

View Demo

Quick Tip: Outline Elements on Hover

Quick Tip: Outline Elements on Hover:

Someone on the jQuery Google Group yesterday asked about how to display a border around elements on hover. Here is a quick script I wrote to achieve that effect:

[inline][/inline]
JavaScript:
  1. $(document).ready(function() {
  2. $('.entrytext')
  3. .mouseover(function(event) {
  4. $(event.target).addClass('outline-element');
  5. })
  6. .mouseout(function(event) {
  7. $(event.target).removeClass('outline-element');
  8. })
  9. .click(function(event) {
  10. $(event.target).toggleClass('outline-element-clicked');
  11. });
  12. });

The script will respond to mouseover, mouseout, and click within <div class="entrytext">. I use mouseover/mouseout rather than mouseenter/mouseleave because the script relies on event bubbling. The $(event.target) selector ensures that the class manipulation occurs on the innermost element. You can try it out on this page by hovering over elements within the main content area.

The two classes that are being toggled have the following style rules:

CSS:
  1. .outlineElement {
  2. outline: 1px solid #c00;
  3. }
  4. .outlineElementClicked {
  5. outline: 1px solid #0c0;
  6. }

The outline property is really handy for this sort of thing because it doesn't affect the element's dimensions or layout. Unfortunately, it isn't supported in Internet Explorer 6 or 7, so if you want to support them, you might want to use border or background-color instead in a separate IE-only stylesheet. Or, maybe IE has some proprietary property? Anyone know?

I'm not sure what the person wanted to do with this script. By itself, it doesn't seem particularly useful to me. Still, I think there are a few concepts in there that can be applied elsewhere, and maybe the script can be expanded to do something interesting.

Update

After thinking about this for a night, I realized that the script could be written more succinctly by combining the mouseover and mouseout event types in a single .bind() method and toggling the class therein …

JavaScript:
  1. $(document).ready(function() {
  2. $('.entrytext')
  3. .bind('mouseover mouseout', function(event) {
  4. var $tgt = $(event.target);
  5. if (!$tgt.closest('.syntax_hilite').length) {
  6. $tgt.toggleClass('outline-element');
  7. }
  8. })
  9. .click(function(event) {
  10. $(event.target).toggleClass('outline-element-clicked');
  11. });
  12. });

Notice that the updated script also takes advantage of the .closest() method, which was introduced in jQuery 1.3. I don't want to apply the outline to any of the syntax-highlighted code in the entry, so I make sure that neither the event target nor any of its ancestors has a class of 'syntax_hilite.' Remember, to test for the presence (or absence) of a selector, we need to include more than just the selector itself. Here I test for 'not a length.' What I'm actually looking for is a length of 0, but since 0 is 'falsey' the ! operator works just fine here.

One More Tip

Come to think of it, I can make the script shorter still by including the click event type along with mouseover and mouseout. Then, inside the the handler, the class that is toggled is determined by event.type.

JavaScript:
  1. $(document).ready(function() {
  2. $('.entrytext').bind('mouseover mouseout click', function(event) {
  3. var $tgt = $(event.target);
  4. if (!$tgt.closest('.syntax_hilite').length) {
  5. $tgt.toggleClass(event.type == 'click' ? 'outline-element-clicked' : 'outline-element');
  6. }
  7. });
  8. });

Sometimes when code becomes shorter, it can also get less readable and more difficult to modify later on. I'm not suggesting that the final code snippet is necessarily the best way to achieve the outcome. A lot depends on what else you're planning to do and how a certain piece fits into the project as a whole.

jQuery Infinite Carousel

jQuery Infinite Carousel: "With jQuery for Designer’s redesign I decided to add a scrolling carousel that worked in the same way the carousel worked on the Apple Mac ads page. This tutorial will walk through the fundamentals of the effect and how to recreate it. Watch Watch jQuery Infinite Carousel screencast (Alternative flash version) QuickTime version is approximately 70Mb, flash version [...]"

Tuesday, December 15, 2009

API: queue & dequeue

API: queue & dequeue: "queue & dequeue are a pair of core data utilities that help you to add your own bespoke to animations. Watch Watch API: queue & dequeue screencast (Alternative flash version) QuickTime version is approximately 7Mb, flash version is streaming. View the demo used in the screencast Queue & Dequeue These methods tend to come in pairs when you’re working within [...]"

Quick Tip: Prevent Animation Queue Buildup

Quick Tip: Prevent Animation Queue Buildup: You've probably stumbled upon a navigation powered by some jQuery effects during your web adventures. Of course you then ran your mouse back and forth over the navigation really fast to watch the animation repeat itself over and over and over again. The natural queuing of animations/effects by jQuery makes the typical animation super easy to code and move on to more important tasks. However, sometimes the animation queue just gets in the way. Here is an example of a navigation that has some effects applied to it via jQuery. Mouse back and forth over the links to see how the animations build up. Here is the JavaScript that does the effect for the menu:
JavaScript:
  1. $(document).ready(function() {
  2. $('ul.anim_queue_example1 a')
  3. .hover(function() {
  4. $(this).animate({ left: 20 }, 'fast');
  5. }, function() {
  6. $(this).animate({ left: 0 }, 'fast');
  7. });
  8. });
Now let's look at how we can prevent this undesirable behavior. Ready? Simply call the .stop() method before animating again. Here is the updated JavaScript that fixes the animation queue buildup by using the .stop() method.
JavaScript:
  1. $(document).ready(function() {
  2. $('ul.anim_queue_example2 a')
  3. .hover(function() {
  4. $(this).stop().animate({ left: 20 }, 'fast');
  5. }, function() {
  6. $(this).stop().animate({ left: 0 }, 'fast');
  7. });
  8. });
Here is the navigation again, using the above JavaScript. As you can see, the animation is no longer queued up by moving your mouse back and forth over the menu items. You can even get bonus points for implementing something like the hoverIntent plugin that can add a slight delay before running the animation. Doing so makes sure the animation plays only if the user actually places the mouse on the element. For more information about the .stop() method, visit the jQuery documentation page. To see a real-world example of the .stop() method in action, using its two arguments — clearQueue and goToEnd — check out the script Karl Swedberg put together at Little Orange Star.