From: Mike Sherov Date: Wed, 26 Dec 2012 13:35:42 +0000 (-0500) Subject: Effects: Rewrite X-Git-Tag: 1.12.0-beta.1~470 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b6bec797d6a8ef0b377a866c38c67e66a626b45f;p=jquery-ui.git Effects: Rewrite 1. Introduces a set of helper methods to easily create and define new effects. 2. Uses clip animations and placeholders instead of wrappers for clip effects. 3. Ensures all animations are detectable as animated Fixes #10599 Fixes #9477 Fixes #9257 Fixes #9066 Fixes #8867 Fixes #8671 Fixes #8505 Fixes #7885 Fixes #7041 Closes gh-1017 --- diff --git a/demos/effect/addClass.html b/demos/effect/addClass.html index f594291fe..0aab17304 100644 --- a/demos/effect/addClass.html +++ b/demos/effect/addClass.html @@ -10,8 +10,8 @@ + @@ -23,7 +24,7 @@ + @@ -23,7 +24,7 @@ + @@ -32,7 +33,7 @@ #effect { position: relative; width: 240px; - height: 135px; + height: 170px; padding: 0.4em; } #effect h3 { @@ -52,7 +53,7 @@ var options = {}; // some effects have required parameters if ( selectedEffect === "scale" ) { - options = { percent: 0 }; + options = { percent: 50 }; } else if ( selectedEffect === "size" ) { options = { to: { width: 200, height: 60 } }; } @@ -85,6 +86,7 @@ + diff --git a/tests/unit/effects/effects.html b/tests/unit/effects/effects.html index d6cfdb797..a092ce04b 100644 --- a/tests/unit/effects/effects.html +++ b/tests/unit/effects/effects.html @@ -88,28 +88,47 @@ width: 100px; } + .relative { + position: relative; + top: 0px; + left: 0px; + } + .absolute { + position: absolute; + top: 0px; + left: 0px; + } + .fixed { + position: fixed; + top: 0px; + left: 0px; + } + .static { + position: static; + } +
-
-
- -
-

Child Element Test

-
-
-

Slide with relative width

-
-
-
-
-
- +
+ +
+

Child Element Test

+
+
+

Slide with relative width

+
+
+
+
+
+
+
diff --git a/tests/unit/effects/effects_core.js b/tests/unit/effects/effects_core.js index 05db494e6..0c4e5574f 100644 --- a/tests/unit/effects/effects_core.js +++ b/tests/unit/effects/effects_core.js @@ -1,11 +1,11 @@ (function($) { function present( value, array, message ) { - QUnit.push( jQuery.inArray( value, array ) !== -1 , value, array, message ); + QUnit.push( jQuery.inArray( value, array ) !== -1, value, array, message ); } function notPresent( value, array, message ) { - QUnit.push( jQuery.inArray( value, array ) === -1 , value, array, message ); + QUnit.push( jQuery.inArray( value, array ) === -1, value, array, message ); } // minDuration is used for "short" animate tests where we are only concerned about the final @@ -75,20 +75,6 @@ test( "removeClass", function() { equal( "", element[ 0 ].className ); }); - -/* TODO: Disabled - Can't figure out why this is failing in IE 6/7 -test( "createWrapper and removeWrapper retain focused elements (#7595)", function() { - expect( 2 ); - var test = $( "div.hidden" ).show(), - input = $( "" ).appendTo( test ).focus(); - - $.effects.createWrapper( test ); - equal( document.activeElement, input[ 0 ], "Active element is still input after createWrapper" ); - $.effects.removeWrapper( test ); - equal( document.activeElement, input[ 0 ], "Active element is still input after removeWrapper" ); -}); -*/ - module( "effects.core: animateClass" ); asyncTest( "animateClass works with borderStyle", function() { @@ -213,6 +199,44 @@ asyncTest( "animateClass: css and class changes during animation are not lost (# .height( 100 ); }); +test( "createPlaceholder: only created for static or relative elements", function() { + expect( 4 ); + + ok( $.effects.createPlaceholder( $( ".relative" ) ).length, "placeholder created for relative element" ); + ok( $.effects.createPlaceholder( $( ".static" ) ).length, "placeholder created for static element" ); + ok( !$.effects.createPlaceholder( $( ".absolute" ) ), "placeholder not created for absolute element" ); + ok( !$.effects.createPlaceholder( $( ".fixed" ) ), "placeholder not created for fixed element" ); +}); + +test( "createPlaceholder: preserves layout affecting properties", function() { + expect( 7 ); + + var position = 5, + element = $( ".relative" ).css({ + top: position, + left: position + }), + before = { + offset: element.offset(), + outerWidth: element.outerWidth( true ), + outerHeight: element.outerHeight( true ), + "float": element.css( "float" ), + position: element.position() + }, + placeholder = $.effects.createPlaceholder( element ); + + // Placeholders are only placed to preserve the effect on layout. Considering + // top and left do not change layout, they are not preserved, which makes some + // of the math simpler in the implementation. + deepEqual( before.offset.top - position, placeholder.offset().top, "offset top preserved" ); + deepEqual( before.offset.left - position, placeholder.offset().left, "offset left preserved" ); + deepEqual( before.position.top - position, placeholder.position().top, "position top preserved" ); + deepEqual( before.position.left - position, placeholder.position().left, "position left preserved" ); + + deepEqual( before[ "float" ], placeholder.css( "float" ), "float preserved" ); + deepEqual( before.outerWidth, placeholder.outerWidth( true ), "width preserved" ); + deepEqual( before.outerHeight, placeholder.outerHeight( true ), "height preserved" ); +}); $.each( $.effects.effect, function( effect ) { module( "effects." + effect ); @@ -223,7 +247,7 @@ $.each( $.effects.effect, function( effect ) { return; } asyncTest( "show/hide", function() { - expect( 8 ); + expect( 12 ); var hidden = $( "div.hidden" ), count = 0, test = 0; @@ -242,14 +266,40 @@ $.each( $.effects.effect, function( effect ) { }; } - hidden.queue( queueTest() ).show( effect, minDuration, queueTest(function() { - equal( hidden.css("display"), "block", "Hidden is shown after .show(\"" +effect+ "\", time)" ); - })).queue( queueTest() ).hide( effect, minDuration, queueTest(function() { - equal( hidden.css("display"), "none", "Back to hidden after .hide(\"" +effect+ "\", time)" ); - })).queue( queueTest(function() { - deepEqual( hidden.queue(), ["inprogress"], "Only the inprogress sentinel remains"); - start(); - })); + function duringTest( fn ) { + return function( next ) { + setTimeout( fn ); + next(); + }; + } + + hidden + .queue( queueTest() ) + .queue( duringTest(function() { + ok( hidden.is( ":animated" ), + "Hidden is seen as animated during .show(\"" + effect + "\", time)" ); + }) ) + .show( effect, minDuration, queueTest(function() { + equal( hidden.css( "display" ), "block", + "Hidden is shown after .show(\"" + effect + "\", time)" ); + ok( !$( ".ui-effects-placeholder" ).length, + "No placeholder remains after .show(\"" + effect + "\", time)" ); + }) ) + .queue( queueTest() ) + .queue( duringTest(function() { + ok( hidden.is( ":animated" ), + "Hidden is seen as animated during .hide(\"" + effect + "\", time)" ); + }) ) + .hide( effect, minDuration, queueTest(function() { + equal( hidden.css( "display" ), "none", + "Back to hidden after .hide(\"" + effect + "\", time)" ); + ok( !$( ".ui-effects-placeholder" ).length, + "No placeholder remains after .hide(\"" + effect + "\", time)" ); + }) ) + .queue( queueTest(function() { + deepEqual( hidden.queue(), [ "inprogress" ], "Only the inprogress sentinel remains" ); + start(); + }) ); }); asyncTest( "relative width & height - properties are preserved", function() { diff --git a/tests/unit/effects/effects_scale.js b/tests/unit/effects/effects_scale.js index 6abbcb538..caed39c22 100644 --- a/tests/unit/effects/effects_scale.js +++ b/tests/unit/effects/effects_scale.js @@ -6,8 +6,8 @@ function run( position, v, h, vo, ho ) { asyncTest( desc, function() { expect( 2 ); function complete() { - equal( parseInt( test.css( h ), 10 ), target[ h ], "Horizontal Position Correct " + desc ); - equal( parseInt( test.css( v ), 10 ), target[ v ], "Vertical Position Correct " + desc ); + closeEnough( parseInt( test.css( h ), 10 ), target[ h ], 1, "Horizontal Position Correct " + desc ); + closeEnough( parseInt( test.css( v ), 10 ), target[ v ], 1, "Vertical Position Correct " + desc ); start(); } var test = $( ".testScale" ), diff --git a/tests/visual/effects/all.html b/tests/visual/effects/all.html index 9fb4cf9c4..c1bdd9fa3 100644 --- a/tests/visual/effects/all.html +++ b/tests/visual/effects/all.html @@ -14,8 +14,10 @@ + + diff --git a/tests/visual/effects/clip.html b/tests/visual/effects/clip.html index 49efa6d8b..f56f54189 100644 --- a/tests/visual/effects/clip.html +++ b/tests/visual/effects/clip.html @@ -70,6 +70,7 @@

EXPECTED: Clicking "Toggle" or "Effect Toggle" a second time reverses the animation, first showing all elements at their original dimensions, and restoring them to their original state.

EXPECTED: Clicking "Effect Default" should always perform a "hide" animation.

EXPECTED: Clicking any of the buttons in quick succession should queue the relevant animations.

+

EXPECTED CANTFIX: In IE8, the clip animation jumps due to a bug that causes .css('clip') to return undefined unless the clip property is an inline style.

@@ -80,7 +81,7 @@

Jerky corned beef short loin fatback jowl tail. Rump spare ribs shoulder pork belly. Sausage cow ground round bacon. Bresaola kielbasa pastrami brisket ham hock. Andouille kielbasa ham, pork beef tenderloin ground round beef ribs flank turkey pancetta tri-tip.

Shankle filet mignon ribeye chicken, bacon jowl drumstick frankfurter swine short loin capicola leberkas tenderloin pig. Shankle bacon shank pork loin, shoulder ham drumstick biltong. Shankle ham pastrami ball tip turkey leberkas pork loin ground round. Chicken strip steak venison shoulder biltong ham. Bacon pork loin tenderloin kielbasa, prosciutto sausage leberkas jowl ribeye turducken. Flank short loin venison tenderloin spare ribs boudin, tongue pork chop shank sirloin. Ground round ham pork belly, corned beef jowl strip steak short ribs prosciutto pig bresaola spare ribs.

- jQuery Logo + jQuery Logo

Pork loin biltong ball tip tail jerky beef ribs prosciutto short loin turducken. Turkey chicken jowl pork loin shank tri-tip swine brisket. Doner prosciutto leberkas venison ground round, short loin capicola hamburger pork bacon. Spare ribs beef pork tenderloin rump shoulder pork belly turducken cow beef ribs pastrami tail flank. Spare ribs tri-tip shank, pork beef ribs ribeye chicken bacon boudin shoulder venison. Sirloin beef ribs boudin, andouille doner tail ball tip biltong prosciutto chicken beef turkey tongue hamburger tri-tip.

Doner salami jowl beef ribs. Pork chop beef short loin pork, kielbasa tail andouille salami sausage meatball short ribs t-bone tri-tip ham. Meatball short ribs prosciutto flank chicken fatback frankfurter brisket turducken. Corned beef hamburger swine short ribs pancetta. Jerky bresaola pork chuck spare ribs pastrami shoulder flank chicken leberkas beef.

diff --git a/tests/visual/effects/effects.js b/tests/visual/effects/effects.js index 624e0b128..f0963a99d 100644 --- a/tests/visual/effects/effects.js +++ b/tests/visual/effects/effects.js @@ -57,7 +57,7 @@ effect( "#highlight", "highlight", {} ); effect( "#pulsate", "pulsate", { times: 2 } ); -effect( "#puff", "puff", { times: 2 } ); +effect( "#puff", "puff", {} ); effect( "#scale", "scale", {} ); effect( "#size", "size", {} ); $( "#sizeToggle" ).click(function() { diff --git a/tests/visual/effects/image.png b/tests/visual/effects/image.png new file mode 100644 index 000000000..4ec90439a Binary files /dev/null and b/tests/visual/effects/image.png differ diff --git a/tests/visual/index.html b/tests/visual/index.html index f7d516f20..0c9b14da8 100644 --- a/tests/visual/index.html +++ b/tests/visual/index.html @@ -48,6 +48,7 @@

Effects

diff --git a/ui/effect-blind.js b/ui/effect-blind.js index ffdfa3735..eb96cca6e 100644 --- a/ui/effect-blind.js +++ b/ui/effect-blind.js @@ -28,68 +28,42 @@ } }(function( $ ) { -return $.effects.effect.blind = function( o, done ) { - // Create element - var el = $( this ), - rvertical = /up|down|vertical/, - rpositivemotion = /up|left|vertical|horizontal/, - props = [ "position", "top", "bottom", "left", "right", "height", "width" ], - mode = $.effects.setMode( el, o.mode || "hide" ), - direction = o.direction || "up", - vertical = rvertical.test( direction ), - ref = vertical ? "height" : "width", - ref2 = vertical ? "top" : "left", - motion = rpositivemotion.test( direction ), - animation = {}, - show = mode === "show", - wrapper, distance, margin; +return $.effects.define( "blind", "hide", function( options, done ) { + var map = { + up: [ "bottom", "top" ], + vertical: [ "bottom", "top" ], + down: [ "top", "bottom" ], + left: [ "right", "left" ], + horizontal: [ "right", "left" ], + right: [ "left", "right" ] + }, + element = $( this ), + direction = options.direction || "up", + start = element.cssClip(), + animate = { clip: $.extend( {}, start ) }, + placeholder = $.effects.createPlaceholder( element ); - // if already wrapped, the wrapper's properties are my property. #6245 - if ( el.parent().is( ".ui-effects-wrapper" ) ) { - $.effects.save( el.parent(), props ); - } else { - $.effects.save( el, props ); - } - el.show(); - wrapper = $.effects.createWrapper( el ).css({ - overflow: "hidden" - }); - - distance = wrapper[ ref ](); - margin = parseFloat( wrapper.css( ref2 ) ) || 0; + animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ]; - animation[ ref ] = show ? distance : 0; - if ( !motion ) { - el - .css( vertical ? "bottom" : "right", 0 ) - .css( vertical ? "top" : "left", "auto" ) - .css({ position: "absolute" }); + if ( options.mode === "show" ) { + element.cssClip( animate.clip ); + if ( placeholder ) { + placeholder.css( $.effects.clipToBox( animate ) ); + } - animation[ ref2 ] = show ? margin : distance + margin; + animate.clip = start; } - // start at 0 if we are showing - if ( show ) { - wrapper.css( ref, 0 ); - if ( !motion ) { - wrapper.css( ref2, margin + distance ); - } + if ( placeholder ) { + placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing ); } - // Animate - wrapper.animate( animation, { - duration: o.duration, - easing: o.easing, + element.animate( animate, { queue: false, - complete: function() { - if ( mode === "hide" ) { - el.hide(); - } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - } + duration: options.duration, + easing: options.easing, + complete: done }); -}; +}); })); diff --git a/ui/effect-bounce.js b/ui/effect-bounce.js index b2fa2c951..d599c40b5 100644 --- a/ui/effect-bounce.js +++ b/ui/effect-bounce.js @@ -28,55 +28,47 @@ } }(function( $ ) { -return $.effects.effect.bounce = function( o, done ) { - var el = $( this ), - props = [ "position", "top", "bottom", "left", "right", "height", "width" ], +return $.effects.define( "bounce", function( options, done ) { + var upAnim, downAnim, refValue, + element = $( this ), // defaults: - mode = $.effects.setMode( el, o.mode || "effect" ), + mode = options.mode, hide = mode === "hide", show = mode === "show", - direction = o.direction || "up", - distance = o.distance, - times = o.times || 5, + direction = options.direction || "up", + distance = options.distance, + times = options.times || 5, // number of internal animations anims = times * 2 + ( show || hide ? 1 : 0 ), - speed = o.duration / anims, - easing = o.easing, + speed = options.duration / anims, + easing = options.easing, // utility: ref = ( direction === "up" || direction === "down" ) ? "top" : "left", motion = ( direction === "up" || direction === "left" ), - i, - upAnim, - downAnim, + i = 0, - // we will need to re-assemble the queue to stack our animations in place - queue = el.queue(), - queuelen = queue.length; + queuelen = element.queue().length; - // Avoid touching opacity to prevent clearType and PNG issues in IE - if ( show || hide ) { - props.push( "opacity" ); - } + $.effects.createPlaceholder( element ); - $.effects.save( el, props ); - el.show(); - $.effects.createWrapper( el ); // Create Wrapper + refValue = element.css( ref ); // default distance for the BIGGEST bounce is the outer Distance / 3 if ( !distance ) { - distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; + distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; } if ( show ) { downAnim = { opacity: 1 }; - downAnim[ ref ] = 0; + downAnim[ ref ] = refValue; // if we are showing, force opacity 0 and set the initial position // then do the "first" animation - el.css( "opacity", 0 ) + element + .css( "opacity", 0 ) .css( ref, motion ? -distance * 2 : distance * 2 ) .animate( downAnim, speed, easing ); } @@ -87,13 +79,14 @@ return $.effects.effect.bounce = function( o, done ) { } downAnim = {}; - downAnim[ ref ] = 0; + downAnim[ ref ] = refValue; // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here - for ( i = 0; i < times; i++ ) { + for ( ; i < times; i++ ) { upAnim = {}; upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; - el.animate( upAnim, speed, easing ) + element + .animate( upAnim, speed, easing ) .animate( downAnim, speed, easing ); distance = hide ? distance * 2 : distance / 2; @@ -104,25 +97,12 @@ return $.effects.effect.bounce = function( o, done ) { upAnim = { opacity: 0 }; upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; - el.animate( upAnim, speed, easing ); + element.animate( upAnim, speed, easing ); } - el.queue(function() { - if ( hide ) { - el.hide(); - } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - }); - - // inject all the animations we just queued to be first in line (after "inprogress") - if ( queuelen > 1) { - queue.splice.apply( queue, - [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); - } - el.dequeue(); + element.queue( done ); -}; + $.effects.unshift( element, queuelen, anims + 1 ); +}); })); diff --git a/ui/effect-clip.js b/ui/effect-clip.js index 6a07ad67d..1bb3ebce2 100644 --- a/ui/effect-clip.js +++ b/ui/effect-clip.js @@ -28,55 +28,37 @@ } }(function( $ ) { -return $.effects.effect.clip = function( o, done ) { - // Create element - var el = $( this ), - props = [ "position", "top", "bottom", "left", "right", "height", "width" ], - mode = $.effects.setMode( el, o.mode || "hide" ), - show = mode === "show", - direction = o.direction || "vertical", - vert = direction === "vertical", - size = vert ? "height" : "width", - position = vert ? "top" : "left", - animation = {}, - wrapper, animate, distance; +return $.effects.define( "clip", "hide", function( options, done ) { + var start, + animate = {}, + element = $( this ), + direction = options.direction || "vertical", + both = direction === "both", + horizontal = both || direction === "horizontal", + vertical = both || direction === "vertical"; - // Save & Show - $.effects.save( el, props ); - el.show(); + start = element.cssClip(); + animate.clip = { + top: vertical ? ( start.bottom - start.top ) / 2 : start.top, + right: horizontal ? ( start.right - start.left ) / 2 : start.right, + bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom, + left: horizontal ? ( start.right - start.left ) / 2 : start.left + }; - // Create Wrapper - wrapper = $.effects.createWrapper( el ).css({ - overflow: "hidden" - }); - animate = ( el[0].tagName === "IMG" ) ? wrapper : el; - distance = animate[ size ](); + $.effects.createPlaceholder( element ); - // Shift - if ( show ) { - animate.css( size, 0 ); - animate.css( position, distance / 2 ); + if ( options.mode === "show" ) { + element.cssClip( animate.clip ); + animate.clip = start; } - // Create Animation Object: - animation[ size ] = show ? distance : 0; - animation[ position ] = show ? 0 : distance / 2; - - // Animate - animate.animate( animation, { + element.animate( animate, { queue: false, - duration: o.duration, - easing: o.easing, - complete: function() { - if ( !show ) { - el.hide(); - } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - } + duration: options.duration, + easing: options.easing, + complete: done }); -}; +}); })); diff --git a/ui/effect-drop.js b/ui/effect-drop.js index 0b3f85557..a990c48e7 100644 --- a/ui/effect-drop.js +++ b/ui/effect-drop.js @@ -28,53 +28,40 @@ } }(function( $ ) { -return $.effects.effect.drop = function( o, done ) { +return $.effects.define( "drop", "hide", function( options, done ) { - var el = $( this ), - props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ], - mode = $.effects.setMode( el, o.mode || "hide" ), + var distance, + element = $( this ), + mode = options.mode, show = mode === "show", - direction = o.direction || "left", + direction = options.direction || "left", ref = ( direction === "up" || direction === "down" ) ? "top" : "left", - motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg", + motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=", + oppositeMotion = ( motion === "+=" ) ? "-=" : "+=", animation = { - opacity: show ? 1 : 0 - }, - distance; + opacity: 0 + }; - // Adjust - $.effects.save( el, props ); - el.show(); - $.effects.createWrapper( el ); + $.effects.createPlaceholder( element ); - distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2; + distance = options.distance || element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2; + + animation[ ref ] = motion + distance; if ( show ) { - el - .css( "opacity", 0 ) - .css( ref, motion === "pos" ? -distance : distance ); - } + element.css( animation ); - // Animation - animation[ ref ] = ( show ? - ( motion === "pos" ? "+=" : "-=" ) : - ( motion === "pos" ? "-=" : "+=" ) ) + - distance; + animation[ ref ] = oppositeMotion + distance; + animation.opacity = 1; + } // Animate - el.animate( animation, { + element.animate( animation, { queue: false, - duration: o.duration, - easing: o.easing, - complete: function() { - if ( mode === "hide" ) { - el.hide(); - } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - } + duration: options.duration, + easing: options.easing, + complete: done }); -}; +}); })); diff --git a/ui/effect-explode.js b/ui/effect-explode.js index 547c6787a..adb05cf5f 100644 --- a/ui/effect-explode.js +++ b/ui/effect-explode.js @@ -28,24 +28,22 @@ } }(function( $ ) { -return $.effects.effect.explode = function( o, done ) { +return $.effects.define( "explode", "hide", function( options, done ) { - var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3, + var i, j, left, top, mx, my, + rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3, cells = rows, - el = $( this ), - mode = $.effects.setMode( el, o.mode || "hide" ), + element = $( this ), + mode = options.mode, show = mode === "show", // show and then visibility:hidden the element before calculating offset - offset = el.show().css( "visibility", "hidden" ).offset(), + offset = element.show().css( "visibility", "hidden" ).offset(), // width and height of a piece - width = Math.ceil( el.outerWidth() / cells ), - height = Math.ceil( el.outerHeight() / rows ), - pieces = [], - - // loop - i, j, left, top, mx, my; + width = Math.ceil( element.outerWidth() / cells ), + height = Math.ceil( element.outerHeight() / rows ), + pieces = []; // children animate complete: function childComplete() { @@ -66,7 +64,7 @@ return $.effects.effect.explode = function( o, done ) { // Create a clone of the now hidden main element that will be absolute positioned // within a wrapper div off the -left and -top equal to size of our pieces - el + element .clone() .appendTo( "body" ) .wrap( "
" ) @@ -93,20 +91,17 @@ return $.effects.effect.explode = function( o, done ) { left: left + ( show ? 0 : mx * width ), top: top + ( show ? 0 : my * height ), opacity: show ? 1 : 0 - }, o.duration || 500, o.easing, childComplete ); + }, options.duration || 500, options.easing, childComplete ); } } function animComplete() { - el.css({ + element.css({ visibility: "visible" }); $( pieces ).remove(); - if ( !show ) { - el.hide(); - } done(); } -}; +}); })); diff --git a/ui/effect-fade.js b/ui/effect-fade.js index 3bde7d6b9..ad7c2134b 100644 --- a/ui/effect-fade.js +++ b/ui/effect-fade.js @@ -28,18 +28,19 @@ } }(function( $ ) { -return $.effects.effect.fade = function( o, done ) { - var el = $( this ), - mode = $.effects.setMode( el, o.mode || "toggle" ); +return $.effects.define( "fade", "toggle", function( options, done ) { + var show = options.mode === "show"; - el.animate({ - opacity: mode - }, { - queue: false, - duration: o.duration, - easing: o.easing, - complete: done - }); -}; + $( this ) + .css( "opacity", show ? 0 : 1 ) + .animate({ + opacity: show ? 1 : 0 + }, { + queue: false, + duration: options.duration, + easing: options.easing, + complete: done + }); +}); })); diff --git a/ui/effect-fold.js b/ui/effect-fold.js index 7776f3cc6..7961ee79f 100644 --- a/ui/effect-fold.js +++ b/ui/effect-fold.js @@ -28,64 +28,61 @@ } }(function( $ ) { -return $.effects.effect.fold = function( o, done ) { +return $.effects.define( "fold", "hide", function( options, done ) { // Create element - var el = $( this ), - props = [ "position", "top", "bottom", "left", "right", "height", "width" ], - mode = $.effects.setMode( el, o.mode || "hide" ), + var element = $( this ), + mode = options.mode, show = mode === "show", hide = mode === "hide", - size = o.size || 15, + size = options.size || 15, percent = /([0-9]+)%/.exec( size ), - horizFirst = !!o.horizFirst, - widthFirst = show !== horizFirst, - ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ], - duration = o.duration / 2, - wrapper, distance, - animation1 = {}, - animation2 = {}; - - $.effects.save( el, props ); - el.show(); - - // Create Wrapper - wrapper = $.effects.createWrapper( el ).css({ - overflow: "hidden" - }); - distance = widthFirst ? - [ wrapper.width(), wrapper.height() ] : - [ wrapper.height(), wrapper.width() ]; + horizFirst = !!options.horizFirst, + ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ], + duration = options.duration / 2, + + placeholder = $.effects.createPlaceholder( element ), + + start = element.cssClip(), + animation1 = { clip: $.extend( {}, start ) }, + animation2 = { clip: $.extend( {}, start ) }, + + distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ], + + queuelen = element.queue().length; if ( percent ) { size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; } + animation1.clip[ ref[ 0 ] ] = size; + animation2.clip[ ref[ 0 ] ] = size; + animation2.clip[ ref[ 1 ] ] = 0; + if ( show ) { - wrapper.css( horizFirst ? { - height: 0, - width: size - } : { - height: size, - width: 0 - }); - } + element.cssClip( animation2.clip ); + if ( placeholder ) { + placeholder.css( $.effects.clipToBox( animation2 ) ); + } - // Animation - animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size; - animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0; + animation2.clip = start; + } // Animate - wrapper - .animate( animation1, duration, o.easing ) - .animate( animation2, duration, o.easing, function() { - if ( hide ) { - el.hide(); + element + .queue(function( next ) { + if ( placeholder ) { + placeholder + .animate( $.effects.clipToBox( animation1 ), duration, options.easing ) + .animate( $.effects.clipToBox( animation2 ), duration, options.easing ); } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - }); -}; + next(); + }) + .animate( animation1, duration, options.easing ) + .animate( animation2, duration, options.easing ) + .queue( done ); + + $.effects.unshift( element, queuelen, 4 ); +}); })); diff --git a/ui/effect-highlight.js b/ui/effect-highlight.js index e3ad3cbc4..fb44aef91 100644 --- a/ui/effect-highlight.js +++ b/ui/effect-highlight.js @@ -28,38 +28,29 @@ } }(function( $ ) { -return $.effects.effect.highlight = function( o, done ) { - var elem = $( this ), - props = [ "backgroundImage", "backgroundColor", "opacity" ], - mode = $.effects.setMode( elem, o.mode || "show" ), +return $.effects.define( "highlight", "show", function( options, done ) { + var element = $( this ), animation = { - backgroundColor: elem.css( "backgroundColor" ) + backgroundColor: element.css( "backgroundColor" ) }; - if (mode === "hide") { + if ( options.mode === "hide" ) { animation.opacity = 0; } - $.effects.save( elem, props ); + $.effects.saveStyle( element ); - elem - .show() + element .css({ backgroundImage: "none", - backgroundColor: o.color || "#ffff99" + backgroundColor: options.color || "#ffff99" }) .animate( animation, { queue: false, - duration: o.duration, - easing: o.easing, - complete: function() { - if ( mode === "hide" ) { - elem.hide(); - } - $.effects.restore( elem, props ); - done(); - } + duration: options.duration, + easing: options.easing, + complete: done }); -}; +}); })); diff --git a/ui/effect-puff.js b/ui/effect-puff.js index 8ea6ded22..607ca77f5 100644 --- a/ui/effect-puff.js +++ b/ui/effect-puff.js @@ -29,37 +29,13 @@ } }(function( $ ) { -return $.effects.effect.puff = function( o, done ) { - var elem = $( this ), - mode = $.effects.setMode( elem, o.mode || "hide" ), - hide = mode === "hide", - percent = parseInt( o.percent, 10 ) || 150, - factor = percent / 100, - original = { - height: elem.height(), - width: elem.width(), - outerHeight: elem.outerHeight(), - outerWidth: elem.outerWidth() - }; - - $.extend( o, { - effect: "scale", - queue: false, +return $.effects.define( "puff", "hide", function( options, done ) { + var newOptions = $.extend( true, {}, options, { fade: true, - mode: mode, - complete: done, - percent: hide ? percent : 100, - from: hide ? - original : - { - height: original.height * factor, - width: original.width * factor, - outerHeight: original.outerHeight * factor, - outerWidth: original.outerWidth * factor - } + percent: parseInt( options.percent, 10 ) || 150 }); - elem.effect( o ); -}; + $.effects.effect.scale.call( this, newOptions, done ); +}); })); diff --git a/ui/effect-pulsate.js b/ui/effect-pulsate.js index 0e82761a3..c28da9ff8 100644 --- a/ui/effect-pulsate.js +++ b/ui/effect-pulsate.js @@ -28,51 +28,36 @@ } }(function( $ ) { -return $.effects.effect.pulsate = function( o, done ) { - var elem = $( this ), - mode = $.effects.setMode( elem, o.mode || "show" ), +return $.effects.define( "pulsate", "show", function( options, done ) { + var element = $( this ), + mode = options.mode, show = mode === "show", hide = mode === "hide", - showhide = ( show || mode === "hide" ), + showhide = show || hide, - // showing or hiding leaves of the "last" animation - anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), - duration = o.duration / anims, + // Showing or hiding leaves off the "last" animation + anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), + duration = options.duration / anims, animateTo = 0, - queue = elem.queue(), - queuelen = queue.length, - i; + i = 1, + queuelen = element.queue().length; - if ( show || !elem.is(":visible")) { - elem.css( "opacity", 0 ).show(); + if ( show || !element.is( ":visible" ) ) { + element.css( "opacity", 0 ).show(); animateTo = 1; } - // anims - 1 opacity "toggles" - for ( i = 1; i < anims; i++ ) { - elem.animate({ - opacity: animateTo - }, duration, o.easing ); + // Anims - 1 opacity "toggles" + for ( ; i < anims; i++ ) { + element.animate( { opacity: animateTo }, duration, options.easing ); animateTo = 1 - animateTo; } - elem.animate({ - opacity: animateTo - }, duration, o.easing); + element.animate( { opacity: animateTo }, duration, options.easing ); - elem.queue(function() { - if ( hide ) { - elem.hide(); - } - done(); - }); + element.queue( done ); - // We just queued up "anims" animations, we need to put them next in the queue - if ( queuelen > 1 ) { - queue.splice.apply( queue, - [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); - } - elem.dequeue(); -}; + $.effects.unshift( element, queuelen, anims + 1 ); +}); })); diff --git a/ui/effect-scale.js b/ui/effect-scale.js index e2e1c0b50..478ca25ba 100644 --- a/ui/effect-scale.js +++ b/ui/effect-scale.js @@ -29,66 +29,27 @@ } }(function( $ ) { -return $.effects.effect.scale = function( o, done ) { +return $.effects.define( "scale", function( options, done ) { // Create element var el = $( this ), - options = $.extend( true, {}, o ), - mode = $.effects.setMode( el, o.mode || "effect" ), - percent = parseInt( o.percent, 10 ) || - ( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ), - direction = o.direction || "both", - origin = o.origin, - original = { - height: el.height(), - width: el.width(), - outerHeight: el.outerHeight(), - outerWidth: el.outerWidth() - }, - factor = { - y: direction !== "horizontal" ? (percent / 100) : 1, - x: direction !== "vertical" ? (percent / 100) : 1 - }; + mode = options.mode, + percent = parseInt( options.percent, 10 ) || + ( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ), - // We are going to pass this effect to the size effect: - options.effect = "size"; - options.queue = false; - options.complete = done; - - // Set default origin and restore for show/hide - if ( mode !== "effect" ) { - options.origin = origin || [ "middle", "center" ]; - options.restore = true; - } - - options.from = o.from || ( mode === "show" ? { - height: 0, - width: 0, - outerHeight: 0, - outerWidth: 0 - } : original ); - options.to = { - height: original.height * factor.y, - width: original.width * factor.x, - outerHeight: original.outerHeight * factor.y, - outerWidth: original.outerWidth * factor.x - }; + newOptions = $.extend( true, { + from: $.effects.scaledDimensions( el ), + to: $.effects.scaledDimensions( el, percent, options.direction || "both" ), + origin: options.origin || [ "middle", "center" ] + }, options ); // Fade option to support puff if ( options.fade ) { - if ( mode === "show" ) { - options.from.opacity = 0; - options.to.opacity = 1; - } - if ( mode === "hide" ) { - options.from.opacity = 1; - options.to.opacity = 0; - } + newOptions.from.opacity = 1; + newOptions.to.opacity = 0; } - // Animate - el.effect( options ); - -}; + $.effects.effect.size.call( this, newOptions, done ); +}); })); diff --git a/ui/effect-shake.js b/ui/effect-shake.js index 896d6f95e..387f21340 100644 --- a/ui/effect-shake.js +++ b/ui/effect-shake.js @@ -28,30 +28,24 @@ } }(function( $ ) { -return $.effects.effect.shake = function( o, done ) { +return $.effects.define( "shake", function( options, done ) { - var el = $( this ), - props = [ "position", "top", "bottom", "left", "right", "height", "width" ], - mode = $.effects.setMode( el, o.mode || "effect" ), - direction = o.direction || "left", - distance = o.distance || 20, - times = o.times || 3, + var i = 1, + element = $( this ), + direction = options.direction || "left", + distance = options.distance || 20, + times = options.times || 3, anims = times * 2 + 1, - speed = Math.round( o.duration / anims ), - ref = (direction === "up" || direction === "down") ? "top" : "left", - positiveMotion = (direction === "up" || direction === "left"), + speed = Math.round( options.duration / anims ), + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + positiveMotion = ( direction === "up" || direction === "left" ), animation = {}, animation1 = {}, animation2 = {}, - i, - // we will need to re-assemble the queue to stack our animations in place - queue = el.queue(), - queuelen = queue.length; + queuelen = element.queue().length; - $.effects.save( el, props ); - el.show(); - $.effects.createWrapper( el ); + $.effects.createPlaceholder( element ); // Animation animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; @@ -59,31 +53,19 @@ return $.effects.effect.shake = function( o, done ) { animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; // Animate - el.animate( animation, speed, o.easing ); + element.animate( animation, speed, options.easing ); // Shakes - for ( i = 1; i < times; i++ ) { - el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing ); + for ( ; i < times; i++ ) { + element.animate( animation1, speed, options.easing ).animate( animation2, speed, options.easing ); } - el - .animate( animation1, speed, o.easing ) - .animate( animation, speed / 2, o.easing ) - .queue(function() { - if ( mode === "hide" ) { - el.hide(); - } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - }); - // inject all the animations we just queued to be first in line (after "inprogress") - if ( queuelen > 1) { - queue.splice.apply( queue, - [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); - } - el.dequeue(); + element + .animate( animation1, speed, options.easing ) + .animate( animation, speed / 2, options.easing ) + .queue( done ); -}; + $.effects.unshift( element, queuelen, anims + 1 ); +}); })); diff --git a/ui/effect-size.js b/ui/effect-size.js index 984d74105..91a6bf51b 100644 --- a/ui/effect-size.js +++ b/ui/effect-size.js @@ -28,63 +28,45 @@ } }(function( $ ) { -return $.effects.effect.size = function( o, done ) { +return $.effects.define( "size", function( options, done ) { // Create element - var original, baseline, factor, - el = $( this ), - props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ], - - // Always restore - props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ], + var baseline, factor, temp, + element = $( this ), // Copy for children - props2 = [ "width", "height", "overflow" ], cProps = [ "fontSize" ], vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], // Set options - mode = $.effects.setMode( el, o.mode || "effect" ), - restore = o.restore || mode !== "effect", - scale = o.scale || "both", - origin = o.origin || [ "middle", "center" ], - position = el.css( "position" ), - props = restore ? props0 : props1, - zero = { - height: 0, - width: 0, - outerHeight: 0, - outerWidth: 0 - }; + mode = options.mode, + restore = mode !== "effect", + scale = options.scale || "both", + origin = options.origin || [ "middle", "center" ], + position = element.css( "position" ), + pos = element.position(), + original = $.effects.scaledDimensions( element ), + from = options.from || original, + to = options.to || $.effects.scaledDimensions( element, 0 ); + + $.effects.createPlaceholder( element ); if ( mode === "show" ) { - el.show(); - } - original = { - height: el.height(), - width: el.width(), - outerHeight: el.outerHeight(), - outerWidth: el.outerWidth() - }; - - if ( o.mode === "toggle" && mode === "show" ) { - el.from = o.to || zero; - el.to = o.from || original; - } else { - el.from = o.from || ( mode === "show" ? zero : original ); - el.to = o.to || ( mode === "hide" ? zero : original ); + temp = from; + from = to; + to = temp; } // Set scaling factor factor = { from: { - y: el.from.height / original.height, - x: el.from.width / original.width + y: from.height / original.height, + x: from.width / original.width }, to: { - y: el.to.height / original.height, - x: el.to.width / original.width + y: to.height / original.height, + x: to.width / original.width } }; @@ -93,16 +75,14 @@ return $.effects.effect.size = function( o, done ) { // Vertical props scaling if ( factor.from.y !== factor.to.y ) { - props = props.concat( vProps ); - el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from ); - el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to ); + from = $.effects.setTransition( element, vProps, factor.from.y, from ); + to = $.effects.setTransition( element, vProps, factor.to.y, to ); } // Horizontal props scaling if ( factor.from.x !== factor.to.x ) { - props = props.concat( hProps ); - el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from ); - el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to ); + from = $.effects.setTransition( element, hProps, factor.from.x, from ); + to = $.effects.setTransition( element, hProps, factor.to.x, to ); } } @@ -111,128 +91,100 @@ return $.effects.effect.size = function( o, done ) { // Vertical props scaling if ( factor.from.y !== factor.to.y ) { - props = props.concat( cProps ).concat( props2 ); - el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from ); - el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to ); + from = $.effects.setTransition( element, cProps, factor.from.y, from ); + to = $.effects.setTransition( element, cProps, factor.to.y, to ); } } - $.effects.save( el, props ); - el.show(); - $.effects.createWrapper( el ); - el.css( "overflow", "hidden" ).css( el.from ); - - // Adjust - if (origin) { // Calculate baseline shifts + // Adjust the position properties based on the provided origin points + if ( origin ) { baseline = $.effects.getBaseline( origin, original ); - el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y; - el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x; - el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y; - el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x; + from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top; + from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left; + to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top; + to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left; } - el.css( el.from ); // set top & left + element.css( from ); - // Animate - if ( scale === "content" || scale === "both" ) { // Scale the children + // Animate the children if desired + if ( scale === "content" || scale === "both" ) { - // Add margins/font-size - vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps); + vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat( cProps ); hProps = hProps.concat([ "marginLeft", "marginRight" ]); - props2 = props0.concat(vProps).concat(hProps); - el.find( "*[width]" ).each( function() { + // Only animate children with width attributes specified + // TODO: is this right? should we include anything with css width specified as well + element.find( "*[width]" ).each( function() { var child = $( this ), - c_original = { - height: child.height(), - width: child.width(), - outerHeight: child.outerHeight(), - outerWidth: child.outerWidth() + childOriginal = $.effects.scaledDimensions( child ), + childFrom = { + height: childOriginal.height * factor.from.y, + width: childOriginal.width * factor.from.x, + outerHeight: childOriginal.outerHeight * factor.from.y, + outerWidth: childOriginal.outerWidth * factor.from.x + }, + childTo = { + height: childOriginal.height * factor.to.y, + width: childOriginal.width * factor.to.x, + outerHeight: childOriginal.height * factor.to.y, + outerWidth: childOriginal.width * factor.to.x }; - if (restore) { - $.effects.save(child, props2); - } - - child.from = { - height: c_original.height * factor.from.y, - width: c_original.width * factor.from.x, - outerHeight: c_original.outerHeight * factor.from.y, - outerWidth: c_original.outerWidth * factor.from.x - }; - child.to = { - height: c_original.height * factor.to.y, - width: c_original.width * factor.to.x, - outerHeight: c_original.height * factor.to.y, - outerWidth: c_original.width * factor.to.x - }; // Vertical props scaling if ( factor.from.y !== factor.to.y ) { - child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from ); - child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to ); + childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom ); + childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo ); } // Horizontal props scaling if ( factor.from.x !== factor.to.x ) { - child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from ); - child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to ); + childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom ); + childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo ); + } + + if ( restore ) { + $.effects.saveStyle( child ); } // Animate children - child.css( child.from ); - child.animate( child.to, o.duration, o.easing, function() { + child.css( childFrom ); + child.animate( childTo, options.duration, options.easing, function() { // Restore children if ( restore ) { - $.effects.restore( child, props2 ); + $.effects.restoreStyle( child ); } }); }); } // Animate - el.animate( el.to, { + element.animate( to, { queue: false, - duration: o.duration, - easing: o.easing, + duration: options.duration, + easing: options.easing, complete: function() { - if ( el.to.opacity === 0 ) { - el.css( "opacity", el.from.opacity ); - } - if ( mode === "hide" ) { - el.hide(); + + var offset = element.offset(); + + if ( to.opacity === 0 ) { + element.css( "opacity", from.opacity ); } - $.effects.restore( el, props ); + if ( !restore ) { + element + .css( "position", position === "static" ? "relative" : position ) + .offset( offset ); - // we need to calculate our new positioning based on the scaling - if ( position === "static" ) { - el.css({ - position: "relative", - top: el.to.top, - left: el.to.left - }); - } else { - $.each([ "top", "left" ], function( idx, pos ) { - el.css( pos, function( _, str ) { - var val = parseInt( str, 10 ), - toRef = idx ? el.to.left : el.to.top; - - // if original was "auto", recalculate the new value from wrapper - if ( str === "auto" ) { - return toRef + "px"; - } - - return val + toRef + "px"; - }); - }); - } + // Need to save style here so that automatic style restoration + // doesn't restore to the original styles from before the animation. + $.effects.saveStyle( element ); } - $.effects.removeWrapper( el ); done(); } }); -}; +}); })); diff --git a/ui/effect-slide.js b/ui/effect-slide.js index ec1fec629..03086a928 100644 --- a/ui/effect-slide.js +++ b/ui/effect-slide.js @@ -28,52 +28,47 @@ } }(function( $ ) { -return $.effects.effect.slide = function( o, done ) { - - // Create element - var el = $( this ), - props = [ "position", "top", "bottom", "left", "right", "width", "height" ], - mode = $.effects.setMode( el, o.mode || "show" ), - show = mode === "show", - direction = o.direction || "left", - ref = (direction === "up" || direction === "down") ? "top" : "left", - positiveMotion = (direction === "up" || direction === "left"), - distance, +return $.effects.define( "slide", "show", function( options, done ) { + var startClip, startRef, + element = $( this ), + map = { + up: [ "bottom", "top" ], + down: [ "top", "bottom" ], + left: [ "right", "left" ], + right: [ "left", "right" ] + }, + mode = options.mode, + direction = options.direction || "left", + ref = ( direction === "up" || direction === "down" ) ? "top" : "left", + positiveMotion = ( direction === "up" || direction === "left" ), + distance = options.distance || element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ), animation = {}; - // Adjust - $.effects.save( el, props ); - el.show(); - distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ); + $.effects.createPlaceholder( element ); - $.effects.createWrapper( el ).css({ - overflow: "hidden" - }); + startClip = element.cssClip(); + startRef = element.position()[ ref ]; - if ( show ) { - el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance ); - } + // Define hide animation + animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef; + animation.clip = element.cssClip(); + animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ]; - // Animation - animation[ ref ] = ( show ? - ( positiveMotion ? "+=" : "-=") : - ( positiveMotion ? "-=" : "+=")) + - distance; + // Reverse the animation if we're showing + if ( mode === "show" ) { + element.cssClip( animation.clip ); + element.css( ref, animation[ ref ] ); + animation.clip = startClip; + animation[ ref ] = startRef; + } - // Animate - el.animate( animation, { + // Actually animate + element.animate( animation, { queue: false, - duration: o.duration, - easing: o.easing, - complete: function() { - if ( mode === "hide" ) { - el.hide(); - } - $.effects.restore( el, props ); - $.effects.removeWrapper( el ); - done(); - } + duration: options.duration, + easing: options.easing, + complete: done }); -}; +}); })); diff --git a/ui/effect-transfer.js b/ui/effect-transfer.js index 1008e5b47..78f12881e 100644 --- a/ui/effect-transfer.js +++ b/ui/effect-transfer.js @@ -28,35 +28,10 @@ } }(function( $ ) { -return $.effects.effect.transfer = function( o, done ) { - var elem = $( this ), - target = $( o.to ), - targetFixed = target.css( "position" ) === "fixed", - body = $("body"), - fixTop = targetFixed ? body.scrollTop() : 0, - fixLeft = targetFixed ? body.scrollLeft() : 0, - endPosition = target.offset(), - animation = { - top: endPosition.top - fixTop, - left: endPosition.left - fixLeft, - height: target.innerHeight(), - width: target.innerWidth() - }, - startPosition = elem.offset(), - transfer = $( "
" ) - .appendTo( document.body ) - .addClass( o.className ) - .css({ - top: startPosition.top - fixTop, - left: startPosition.left - fixLeft, - height: elem.innerHeight(), - width: elem.innerWidth(), - position: targetFixed ? "fixed" : "absolute" - }) - .animate( animation, o.duration, o.easing, function() { - transfer.remove(); - done(); - }); -}; +if ( $.uiBackCompat !== false ) { + return $.effects.define( "transfer", function( options, done ) { + $( this ).transfer( options, done ); + }); +} })); diff --git a/ui/effect.js b/ui/effect.js index fbbd7fe28..4507ea006 100644 --- a/ui/effect.js +++ b/ui/effect.js @@ -26,6 +26,8 @@ }(function( $ ) { var dataSpace = "ui-effects-", + dataSpaceStyle = "ui-effects-style", + dataSpaceAnimated = "ui-effects-animated", // Create a local jQuery because jQuery Color relies on it and the // global may not exist with AMD and a custom build (#10199) @@ -908,155 +910,328 @@ $.fn.extend({ (function() { +if ( $.expr && $.expr.filters && $.expr.filters.animated ) { + $.expr.filters.animated = (function( orig ) { + return function( elem ) { + return !!$( elem ).data( dataSpaceAnimated ) || orig( elem ); + }; + })( $.expr.filters.animated ); +} + +if ( $.uiBackCompat !== false ) { + $.extend( $.effects, { + // Saves a set of properties in a data storage + save: function( element, set ) { + var i = 0, length = set.length; + for ( ; i < length; i++ ) { + if ( set[ i ] !== null ) { + element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); + } + } + }, + + // Restores a set of previously saved properties from a data storage + restore: function( element, set ) { + var val, i = 0, length = set.length; + for ( ; i < length; i++ ) { + if ( set[ i ] !== null ) { + val = element.data( dataSpace + set[ i ] ); + // support: jQuery 1.6.2 + // http://bugs.jquery.com/ticket/9917 + // jQuery 1.6.2 incorrectly returns undefined for any falsy value. + // We can't differentiate between "" and 0 here, so we just assume + // empty string since it's likely to be a more common value... + if ( val === undefined ) { + val = ""; + } + element.css( set[ i ], val ); + } + } + }, + + setMode: function( el, mode ) { + if ( mode === "toggle" ) { + mode = el.is( ":hidden" ) ? "show" : "hide"; + } + return mode; + }, + + // Wraps the element around a wrapper that copies position properties + createWrapper: function( element ) { + + // if the element is already wrapped, return it + if ( element.parent().is( ".ui-effects-wrapper" ) ) { + return element.parent(); + } + + // wrap the element + var props = { + width: element.outerWidth( true ), + height: element.outerHeight( true ), + "float": element.css( "float" ) + }, + wrapper = $( "
" ) + .addClass( "ui-effects-wrapper" ) + .css({ + fontSize: "100%", + background: "transparent", + border: "none", + margin: 0, + padding: 0 + }), + // Store the size in case width/height are defined in % - Fixes #5245 + size = { + width: element.width(), + height: element.height() + }, + active = document.activeElement; + + // support: Firefox + // Firefox incorrectly exposes anonymous content + // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 + try { + active.id; + } catch ( e ) { + active = document.body; + } + + element.wrap( wrapper ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).focus(); + } + + wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element + + // transfer positioning properties to the wrapper + if ( element.css( "position" ) === "static" ) { + wrapper.css({ position: "relative" }); + element.css({ position: "relative" }); + } else { + $.extend( props, { + position: element.css( "position" ), + zIndex: element.css( "z-index" ) + }); + $.each([ "top", "left", "bottom", "right" ], function(i, pos) { + props[ pos ] = element.css( pos ); + if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { + props[ pos ] = "auto"; + } + }); + element.css({ + position: "relative", + top: 0, + left: 0, + right: "auto", + bottom: "auto" + }); + } + element.css(size); + + return wrapper.css( props ).show(); + }, + + removeWrapper: function( element ) { + var active = document.activeElement; + + if ( element.parent().is( ".ui-effects-wrapper" ) ) { + element.parent().replaceWith( element ); + + // Fixes #7595 - Elements lose focus when wrapped. + if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { + $( active ).focus(); + } + } + + return element; + } + }); +} + $.extend( $.effects, { version: "@VERSION", - // Saves a set of properties in a data storage - save: function( element, set ) { - for ( var i = 0; i < set.length; i++ ) { - if ( set[ i ] !== null ) { - element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); - } + define: function( name, mode, effect ) { + if ( !effect ) { + effect = mode; + mode = "effect"; } + + $.effects.effect[ name ] = effect; + $.effects.effect[ name ].mode = mode; + + return effect; }, - // Restores a set of previously saved properties from a data storage - restore: function( element, set ) { - var val, i; - for ( i = 0; i < set.length; i++ ) { - if ( set[ i ] !== null ) { - val = element.data( dataSpace + set[ i ] ); - // support: jQuery 1.6.2 - // http://bugs.jquery.com/ticket/9917 - // jQuery 1.6.2 incorrectly returns undefined for any falsy value. - // We can't differentiate between "" and 0 here, so we just assume - // empty string since it's likely to be a more common value... - if ( val === undefined ) { - val = ""; - } - element.css( set[ i ], val ); - } + scaledDimensions: function( element, percent, direction ) { + if ( percent === 0 ) { + return { + height: 0, + width: 0, + outerHeight: 0, + outerWidth: 0 + }; } + + var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1, + y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1; + + return { + height: element.height() * y, + width: element.width() * x, + outerHeight: element.outerHeight() * y, + outerWidth: element.outerWidth() * x + }; + + }, + + clipToBox: function( animation ) { + return { + width: animation.clip.right - animation.clip.left, + height: animation.clip.bottom - animation.clip.top, + left: animation.clip.left, + top: animation.clip.top + }; + }, + + // Injects recently queued functions to be first in line (after "inprogress") + unshift: function( element, queueLength, count ) { + var queue = element.queue(); + + if ( queueLength > 1 ) { + queue.splice.apply( queue, + [ 1, 0 ].concat( queue.splice( queueLength, count ) ) ); + } + element.dequeue(); + }, + + saveStyle: function( element ) { + element.data( dataSpaceStyle, element[ 0 ].style.cssText ); + }, + + restoreStyle: function( element ) { + element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || ""; + element.removeData( dataSpaceStyle ); }, - setMode: function( el, mode ) { - if (mode === "toggle") { - mode = el.is( ":hidden" ) ? "show" : "hide"; + mode: function( element, mode ) { + var hidden = element.is( ":hidden" ); + + if ( mode === "toggle" ) { + mode = hidden ? "show" : "hide"; + } + if ( hidden ? mode === "hide" : mode === "show" ) { + mode = "none"; } return mode; }, // Translates a [top,left] array into a baseline value - // this should be a little more flexible in the future to handle a string & hash getBaseline: function( origin, original ) { var y, x; + switch ( origin[ 0 ] ) { - case "top": y = 0; break; - case "middle": y = 0.5; break; - case "bottom": y = 1; break; - default: y = origin[ 0 ] / original.height; + case "top": + y = 0; + break; + case "middle": + y = 0.5; + break; + case "bottom": + y = 1; + break; + default: + y = origin[ 0 ] / original.height; } + switch ( origin[ 1 ] ) { - case "left": x = 0; break; - case "center": x = 0.5; break; - case "right": x = 1; break; - default: x = origin[ 1 ] / original.width; + case "left": + x = 0; + break; + case "center": + x = 0.5; + break; + case "right": + x = 1; + break; + default: + x = origin[ 1 ] / original.width; } + return { x: x, y: y }; }, - // Wraps the element around a wrapper that copies position properties - createWrapper: function( element ) { - - // if the element is already wrapped, return it - if ( element.parent().is( ".ui-effects-wrapper" )) { - return element.parent(); - } - - // wrap the element - var props = { - width: element.outerWidth(true), - height: element.outerHeight(true), + // Creates a placeholder element so that the original element can be made absolute + createPlaceholder: function( element ) { + var placeholder, + cssPosition = element.css( "position" ), + position = element.position(); + + // Lock in margins first to account for form elements, which + // will change margin if you explicitly set height + // see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380 + // Support: Safari + element.css({ + marginTop: element.css( "marginTop" ), + marginBottom: element.css( "marginBottom" ), + marginLeft: element.css( "marginLeft" ), + marginRight: element.css( "marginRight" ) + }) + .outerWidth( element.outerWidth() ) + .outerHeight( element.outerHeight() ); + + if ( /^(static|relative)/.test( cssPosition ) ) { + cssPosition = "absolute"; + + placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css({ + + // Convert inline to inline block to account for inline elements + // that turn to inline block based on content (like img) + display: /^(inline|ruby)/.test( element.css( "display" ) ) ? "inline-block" : "block", + visibility: "hidden", + + // Margins need to be set to account for margin collapse + marginTop: element.css( "marginTop" ), + marginBottom: element.css( "marginBottom" ), + marginLeft: element.css( "marginLeft" ), + marginRight: element.css( "marginRight" ), "float": element.css( "float" ) - }, - wrapper = $( "
" ) - .addClass( "ui-effects-wrapper" ) - .css({ - fontSize: "100%", - background: "transparent", - border: "none", - margin: 0, - padding: 0 - }), - // Store the size in case width/height are defined in % - Fixes #5245 - size = { - width: element.width(), - height: element.height() - }, - active = document.activeElement; - - // support: Firefox - // Firefox incorrectly exposes anonymous content - // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 - try { - active.id; - } catch ( e ) { - active = document.body; - } + }) + .outerWidth( element.outerWidth() ) + .outerHeight( element.outerHeight() ) + .addClass( "ui-effects-placeholder" ); - element.wrap( wrapper ); - - // Fixes #7595 - Elements lose focus when wrapped. - if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { - $( active ).focus(); + element.data( dataSpace + "placeholder", placeholder ); } - wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element - - // transfer positioning properties to the wrapper - if ( element.css( "position" ) === "static" ) { - wrapper.css({ position: "relative" }); - element.css({ position: "relative" }); - } else { - $.extend( props, { - position: element.css( "position" ), - zIndex: element.css( "z-index" ) - }); - $.each([ "top", "left", "bottom", "right" ], function(i, pos) { - props[ pos ] = element.css( pos ); - if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { - props[ pos ] = "auto"; - } - }); - element.css({ - position: "relative", - top: 0, - left: 0, - right: "auto", - bottom: "auto" - }); - } - element.css(size); + element.css({ + position: cssPosition, + left: position.left, + top: position.top + }); - return wrapper.css( props ).show(); + return placeholder; }, - removeWrapper: function( element ) { - var active = document.activeElement; + removePlaceholder: function( element ) { + var dataKey = dataSpace + "placeholder", + placeholder = element.data( dataKey ); - if ( element.parent().is( ".ui-effects-wrapper" ) ) { - element.parent().replaceWith( element ); - - // Fixes #7595 - Elements lose focus when wrapped. - if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { - $( active ).focus(); - } + if ( placeholder ) { + placeholder.remove(); + element.removeData( dataKey ); } + }, - return element; + // Removes a placeholder if it exists and restores + // properties that were modified during placeholder creation + cleanUp: function( element ) { + $.effects.restoreStyle( element ); + $.effects.removePlaceholder( element ); }, setTransition: function( element, list, factor, value ) { @@ -1152,48 +1327,109 @@ function standardAnimationOption( option ) { $.fn.extend({ effect: function( /* effect, options, speed, callback */ ) { var args = _normalizeArguments.apply( this, arguments ), - mode = args.mode, + effectMethod = $.effects.effect[ args.effect ], + defaultMode = effectMethod.mode, queue = args.queue, - effectMethod = $.effects.effect[ args.effect ]; + queueName = queue || "fx", + complete = args.complete, + mode = args.mode, + modes = [], + prefilter = function( next ) { + var el = $( this ), + normalizedMode = $.effects.mode( el, mode ) || defaultMode; + + // Sentinel for duck-punching the :animated psuedo-selector + el.data( dataSpaceAnimated, true ); + + // Save effect mode for later use, + // we can't just call $.effects.mode again later, + // as the .show() below destroys the initial state + modes.push( normalizedMode ); + + // See $.uiBackCompat inside of run() for removal of defaultMode in 1.13 + if ( defaultMode && ( normalizedMode === "show" || + ( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) { + el.show(); + } + + if ( !defaultMode || normalizedMode !== "none" ) { + $.effects.saveStyle( el ); + } + + if ( $.isFunction( next ) ) { + next(); + } + }; if ( $.fx.off || !effectMethod ) { // delegate to the original method (e.g., .show()) if possible if ( mode ) { - return this[ mode ]( args.duration, args.complete ); + return this[ mode ]( args.duration, complete ); } else { return this.each( function() { - if ( args.complete ) { - args.complete.call( this ); + if ( complete ) { + complete.call( this ); } }); } } function run( next ) { - var elem = $( this ), - complete = args.complete, - mode = args.mode; + var elem = $( this ); + + function cleanup() { + elem.removeData( dataSpaceAnimated ); + + $.effects.cleanUp( elem ); + + if ( args.mode === "hide" ) { + elem.hide(); + } + + done(); + } function done() { if ( $.isFunction( complete ) ) { - complete.call( elem[0] ); + complete.call( elem[ 0 ] ); } + if ( $.isFunction( next ) ) { next(); } } - // If the element already has the correct final state, delegate to - // the core methods so the internal tracking of "olddisplay" works. - if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { - elem[ mode ](); - done(); + // Override mode option on a per element basis, + // as toggle can be either show or hide depending on element state + args.mode = modes.shift(); + + if ( $.uiBackCompat !== false && !defaultMode ) { + if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { + + // Call the core method to track "olddisplay" properly + elem[ mode ](); + done(); + } else { + effectMethod.call( elem[ 0 ], args, done ); + } } else { - effectMethod.call( elem[0], args, done ); + if ( args.mode === "none" ) { + + // Call the core method to track "olddisplay" properly + elem[ mode ](); + done(); + } else { + effectMethod.call( elem[ 0 ], args, cleanup ); + } } } - return queue === false ? this.each( run ) : this.queue( queue || "fx", run ); + // Run prefilter on all elements first to ensure that + // any showing or hiding happens before placeholder creation, + // which ensures that any layout changes are correctly captured. + return queue === false ? + this.each( prefilter ).each( run ) : + this.queue( queueName, prefilter ).queue( queueName, run ); }, show: (function( orig ) { @@ -1232,7 +1468,6 @@ $.fn.extend({ }; })( $.fn.toggle ), - // helper functions cssUnit: function(key) { var style = this.css( key ), val = []; @@ -1243,9 +1478,77 @@ $.fn.extend({ } }); return val; + }, + + cssClip: function( clipObj ) { + return clipObj ? + this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " + clipObj.bottom + "px " + clipObj.left + "px)" ) : + parseClip( this.css("clip"), this ); + }, + + transfer: function( options, done ) { + var element = $( this ), + target = $( options.to ), + targetFixed = target.css( "position" ) === "fixed", + body = $( "body" ), + fixTop = targetFixed ? body.scrollTop() : 0, + fixLeft = targetFixed ? body.scrollLeft() : 0, + endPosition = target.offset(), + animation = { + top: endPosition.top - fixTop, + left: endPosition.left - fixLeft, + height: target.innerHeight(), + width: target.innerWidth() + }, + startPosition = element.offset(), + transfer = $( "
" ) + .appendTo( "body" ) + .addClass( options.className ) + .css({ + top: startPosition.top - fixTop, + left: startPosition.left - fixLeft, + height: element.innerHeight(), + width: element.innerWidth(), + position: targetFixed ? "fixed" : "absolute" + }) + .animate( animation, options.duration, options.easing, function() { + transfer.remove(); + done(); + }); } }); +function parseClip( str, element ) { + var outerWidth = element.outerWidth(), + outerHeight = element.outerHeight(), + clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/, + values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ]; + + return { + top: parseFloat( values[ 1 ] ) || 0, + right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ), + bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ), + left: parseFloat( values[ 4 ] ) || 0 + }; +} + +$.fx.step.clip = function( fx ) { + if ( !fx.clipInit ) { + fx.start = $( fx.elem ).cssClip(); + if ( typeof fx.end === "string" ) { + fx.end = parseClip( fx.end, fx.elem ); + } + fx.clipInit = true; + } + + $( fx.elem ).cssClip({ + top: fx.pos * (fx.end.top - fx.start.top) + fx.start.top, + right: fx.pos * (fx.end.right - fx.start.right) + fx.start.right, + bottom: fx.pos * (fx.end.bottom - fx.start.bottom) + fx.start.bottom, + left: fx.pos * (fx.end.left - fx.start.left) + fx.start.left + }); +}; + })(); /******************************************************************************/