views:

1444

answers:

3

I am animating some error/validation elements on a page. I want them to bounce and be highlighted, but at the same time if possible. Here's what I'm currently doing:

var els = $(".errorMsg");
els.effect("bounce", {times: 5}, 100);
els.effect("highlight", {color: "#ffb0aa"}, 300);

This causes the elements to first bounce, and THEN be highlighted, and I'd like them to occur simultaneously. I know that with .animate() you can specify queue:false in the options, but I don't want to use animate because the pre-built effects "bounce" and "highlight" are exactly what I want.

I have tried simply chaining the calls like els.effect().effect(), and that doesn't work. I've also tried to put queue:false in the options object I pass in, and that doesn't work.

+1  A: 

You could try this:

var els = $(".errorMsg");
setTimeout(function() {
    els.effect("bounce", {times: 5}, 100);
}, 1);
setTimeout(function() {
    els.effect("highlight", {color: "#ffb0aa"}, 300);
}, 1);

That should call both the effects at roughly the same time, asynchronously.

kevingessner
I'll give this a shot tomorrow when I'm back at the code, if it works, I'll vote up and mark as the answer. Thanks!
Samuel Meacham
I doubt this will work while the straight forward method fails. You are still calling the `effect` function one by one. Remember Javascript is ran single threaded, so both are going to execute linearly.
LiraNuna
LiraNuna called it. The animations still queue, and the effects execute one after the other. However, LiraNuna, while javascript might be single threaded, it is possible to have 2 effects execute in a way that appears to be simultaneous. You can certainly do it using jquery's animate(), by providing {queue:false} in the options. Before multi-threaded/multi-core CPUs, OSes used time slicing to run multiple threads. JS isn't all that different.
Samuel Meacham
Good call, LiraNuna. I've had good luck using this technique to get "background" JS working - browsers run setTimeout functions asynchronously. jQuery's effects must run synchronously regardless.
kevingessner
+2  A: 

jQuery UI's effects queue animations, so write your own version of a bounce/highlight function. Just copy the source code from both into a single function, clean up the code, and each time it calls animate, make sure to have the bounce and highlight logic in there together.

DaveS
I've considered this option, and I may fall back on it if I really really need both effects. For now, I'm content to have the code simpler, and use just 1 of the effects. It's too bad that jquery allows you to specify {queue:false} for calls to animate(), but not effect().
Samuel Meacham
+1  A: 

Ok, so this is a very custom solution that combines the bounce and highlight effects. I'd rather see some kind of jquery support for combining these more easily, specifying {queue:false}, but I don't think it's that simple.

What I did was take the jquery.effects.bounce.js and jquery.effects.highlight.js (from jquery-ui-1.8rc3), and combine the code of the two as DaveS suggested, to create a new effect named "hibounce". In my testing, it supports all of the options of both, and they occur simultaneously. It looks great! I'm not a huge fan of solutions like this though, because of the maintenance factor. Anytime I upgrade jquery.ui, I'll have to update this file manually as well.

Anyway, here is the combined result (jquery.effects.hibounce.js)

(function($) {

$.effects.hibounce = function(o) {
    return this.queue(function() {
        // Highlight and bounce parts, combined
        var el = $(this),
            props = ['position','top','left','backgroundImage', 'backgroundColor', 'opacity'],
            mode = $.effects.setMode(el, o.options.mode || 'show'),
            animation = {
                backgroundColor: el.css('backgroundColor')
            };

        // From highlight
        if (mode == 'hide') {
            animation.opacity = 0;
        }

        $.effects.save(el, props);

        // From bounce
        // Set options
        var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode
        var direction = o.options.direction || 'up'; // Default direction
        var distance = o.options.distance || 20; // Default distance
        var times = o.options.times || 5; // Default # of times
        var speed = o.duration || 250; // Default speed per bounce
        if (/show|hide/.test(mode)) props.push('opacity'); // Avoid touching opacity to prevent clearType and PNG issues in IE


        // Adjust
        $.effects.save(el, props); el.show(); // Save & Show
        $.effects.createWrapper(el); // Create Wrapper
        var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left';
        var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg';
        var distance = o.options.distance || (ref == 'top' ? el.outerHeight({margin:true}) / 3 : el.outerWidth({margin:true}) / 3);
        if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift
        if (mode == 'hide') distance = distance / (times * 2);
        if (mode != 'hide') times--;

        // from highlight
        el
            .show()
            .css({
                backgroundImage: 'none',
                backgroundColor: o.options.color || '#ffff99'
            })
            .animate(animation, {
                queue: false,
                duration: o.duration * times * 1.3, // cause the hilight to finish just after the bounces (looks best)
                easing: o.options.easing,
                complete: function() {
                    (mode == 'hide' && el.hide());
                    $.effects.restore(el, props);
                    (mode == 'show' && !$.support.opacity && this.style.removeAttribute('filter'));
                    (o.callback && o.callback.apply(this, arguments));
                    el.dequeue();
                }
            });

        // Animate bounces
        if (mode == 'show') { // Show Bounce
            var animation = {opacity: 1};
            animation[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
            el.animate(animation, speed / 2, o.options.easing);
            distance = distance / 2;
            times--;
        };
        for (var i = 0; i < times; i++) { // Bounces
            var animation1 = {}, animation2 = {};
            animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
            animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
            el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing);
            distance = (mode == 'hide') ? distance * 2 : distance / 2;
        };
        if (mode == 'hide') { // Last Bounce
            var animation = {opacity: 0};
            animation[ref] = (motion == 'pos' ? '-=' : '+=')  + distance;
            el.animate(animation, speed / 2, o.options.easing, function(){
                el.hide(); // Hide
                $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
                if(o.callback) o.callback.apply(this, arguments); // Callback
            });
        } else {
            var animation1 = {}, animation2 = {};
            animation1[ref] = (motion == 'pos' ? '-=' : '+=') + distance;
            animation2[ref] = (motion == 'pos' ? '+=' : '-=') + distance;
            el.animate(animation1, speed / 2, o.options.easing).animate(animation2, speed / 2, o.options.easing, function(){
                $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore
                if(o.callback) o.callback.apply(this, arguments); // Callback
            });
        };
        el.queue('fx', function() { el.dequeue(); });
        el.dequeue();
    });
};

})(jQuery);

It can be used like any other effect now:

var el = $("#div1");
el.effect("hibounce", {color: "#F00", times: 5}, 100);
Samuel Meacham