Animations for can-stache
In your stache template, use can-import
to pull can-stache-animate
into the template.
Then bind to a property or event, and provide the desired animation.
<can-import from="can-stache-animate" {^value.animations}="*animations" />
<div (. shake)="*animations.shake"></div>
Note: The *
syntax is necessary to use the can-import
tag to pull a value into the scope.
Note: Use {^value.animations}
to get the animations object, and set that to a variable (*animations
) in the scope.
To create your own custom animations, create a file (we'll call it custom-animations.js
),
and import can-stache-animate
. Use the registerAnimation
and registerAnimations
methods to register your animations.
import $ from "jquery";
import canStacheAnimate from "can-stache-animate";
//sets the global duration
canStacheAnimate.setDuration( 600 );
//register single animation
canStacheAnimate.registerAnimation( "myCustomAnimation", function( el, ev, options ) {
$( el ).animate( {
/* ... */
} );
} );
/* OR */
//register multiple animations
canStacheAnimate.registerAnimations( {
myCustomAnimation: function( el, ev, options ) {
$( el ).animate( {
/* ... */
} );
}
// other animations
} );
export default canStacheAnimate;
Then import your custom file instead of can-stache-animate
.
<can-import from="my-project/custom-animations" {^value.animations}="*animations" />
<div (. shake)="*animations.myCustomAnimation"></div>
Animations can be a string
, a function
, or an object
. Let's take a look at how the different types work.
Object
If an animation is an object, it is expected to have the properties:
Object.run
- function - requiredThe run
method is the core animation method and is required.
Object.before
- function - optionalThe before
method is called prior to the run
method. It is typically used to 'set the stage' for the animation – setting the position
or z-index
css properties of a parent element, for example.
Object.after
- function - optionalThe after
method is called when the run
method has completed. It is typically used to clean up any css properties or state from the element that were only needed for the animation itself and are no longer needed - resetting the position
or z-index
css properties of a parent element, for example.
element
- the element on which the animation is being usedevent
- the event that triggered the animationoptions
- a set of options such as duration
Promise
.Example:
canStacheAnimate.registerAnimation( "myCustomShakeAnimation", {
before: function( vm, el, ev ) {
const $el = $( el );
$el.parent().css( {
"position": "relative"
} );
$el.css( {
"position": "absolute"
} );
},
run: function( vm, el, ev ) {
const $el = $( el );
return $el.animate( {
"left": "-100px"
} ).promise().then( function() {
return $el.animate( {
"left": "100px"
} ).promise().then( function() {
return $el.animate( {
"left": "0px"
} ).promise();
} );
} ).promise();
},
after: function( vm, el, ev ) {
const $el = $( el );
$el.parent().css( {
"position": ""
} );
$el.css( {
"position": ""
} );
}
} );
Note: Returning false from either the before
or run
methods will stop further animations from being executed.
Note: When adding animations to the before
, run
, after
methods, there is no need to us an onComplete
(or similar) callback function. Simply return a Promise
, ans resolve it when any async functionality has completed.
Note: For jQuery animations, you can simply use the .promise()
method.
Object.stop
- function - optionalThe stop
method is called when any one of the before
, run
, or after
methods returns either false
or a Promise
that rejects. It is used to revert any changes that were made during the animation so that the element can be set back to its "ground state".
Example:
canStacheAnimate.registerAnimation( "myCustomHopAnimation", {
duration: 1000,
before: function( el, ev, options ) {
return new Promise( function( resolve, reject ) {
$( el ).animate( {
"margin-top": "-20px"
}, options.duration ).promise().then( function() {
resolve();
} );
//when there is a click in the window,
//reject this promise which will cause
//the stop method to be called
$( window ).one( "click", function() {
reject();
} );
} );
},
run: function( el, ev, options ) {
return $( el ).animate( {
"margin-top": "0px"
}, 400 ).promise();
},
stop: function( el, ev, options ) {
$( el ).stop().animate( {
"margin-top": 0
}, options.duration );
}
} );
If an animation is a function, it is the same as providing that function as an object's run
property and providing null
to the before
and after
properties.
Example:
canStacheAnimate.registerAnimation( "myCustomAnimation", function( el, ev, options ) {
$( el ).animate( {
"opacity": 0.5
} );
} );
If an animation is a string, it is simply set up as an alias to an animation that has already been registered.
Example:
canStacheAnimate.registerAnimation( "myCustomAnimation", "fadeIn" );
Note: "Already registered animations" include the out-of-the-box animations provided by can-stache-animate
.
In addition to a registered animation value being a string identifier of another animation, before
, run
, and after
animation properties can be string identifiers of other animations as well. This is useful to chain animations together or provide modifications to things like duration for an existing animation.
canStacheAnimate.registerAnimations( {
"customFadeIn": function( el, ev, options ) {
return $( el ).animate( {
opacity: 0.8
}, options.duration ).promise();
},
"customFadeOut": function( el, ev, options ) {
return $( el ).animate( {
opacity: 0.2
}, options.duration ).promise();
},
"customPulse": {
before: "customFadeOut",
run: "customFadeIn"
},
"customPulseFast": {
duration: 100,
run: "customPulse"
}
} );
Sometimes it is useful to know when an animation has started or finished a particular phase. We can accomplish this easily with can-util/dom/dispatch.
Example: Register an custom animation, and dispatch events on the element:
import domDispatch from "can-util/dom/dispatch/dispatch";
canStacheAnimate.registerAnimation( "myCustomAnimation", {
before: function( el, ev, options ) {
domDispatch.apply( el, [ "mycustomanimationbefore", [ { test: "foo" } ], false ] );
$( el ).hide().css( {
"opacity": 0
} );
},
run: function( el, ev, options ) {
domDispatch.apply( el, [ "mycustomanimationrunning", [ { test: "foo" } ], false ] );
$( el ).show().animate( {
"opacity": 0
} );
},
after: function( el, ev, options ) {
domDispatch.apply( el, [ "mycustomanimationcomplete", [ { test: "foo" } ], false ] );
}
} );
Listen for those events via stache:
<div
(. animate)="*animationsModule.default.animations.myCustomAnimation"
($mycustomanimationbefore)="handleAnimationBefore"
($mycustomanimationrunning)="handleAnimationRunning"
($mycustomanimationcomplete)="handleAnimationComplete"
/>
Then handle the events from within the scope or viewmodel:
DefineMap.extend( {
handleAnimationBefore( vm, el, ev ) {
console.log( "handleAnimationBefore" );
},
handleAnimationRunning( vm, el, ev ) {
console.log( "handleAnimationRunning" );
},
handleAnimationComplete( vm, el, ev ) {
console.log( "handleAnimationAfter" );
}
} );
Some events require additional setup.
$inserted
When importing animations with can-import
, elements' $inserted
events will fire before the imported animations make it into the scope.
Wait for the animations to be in the scope before rendering the elements like this:
<can-import from="can-stache-animate" {^value.animations}="*animations" />
{{#*animations}}
<div style="display:none;" ($inserted)="*animations.fadeIn" />
{{/*animations}}
Note: Set the element's style initially to display:none;
because the *animations.fadeIn
helper expects the element to not be displayed.
$beforeremove
It is sometimes useful to perform an animation on an element prior to its being removed from the DOM. can-stache-animate
provides a $beforeremove
event that can be used to accomplish this. Here's how it works:
The $beforeremove
event is an async event which means that it has some additional methods that can be used during the event's lifetime.
The most important of these methods are:
event.pause()
Delay the execution of the event's default behavior until event.resume()
is called. In the case above, pausing the $beforeremove
event would delay execution of the $remove
event.
event.resume()
Continue executing the event's default behavior
event.cancel()
Prevent the event's default behavior similar to .preventDefault()
in standard events.
$beforeremove
Here is an example of how to use the async event to write a beforeremove
animation:
canStacheAnimate.registerAnimation( "customFadeOut", {
before: function( el, ev, options ) {
// cancel the event under specified circumstances
if ( $( el ).is( ".cancel" ) ) {
ev.cancel();
// return false to stop the remaining animation methods from running
// (`run` and `after`)
return false;
}
// the event wasn't cancelled, pause it until our animation is done
ev.pause();
},
run: function( el, ev, options ) {
return $( el ).fadeOut().promise();
},
after: function( el, ev, options ) {
// the animation has completed, so we can continue with the default behavior
ev.resume();
}
} );
For example, conditionally render an element based on a scope property:
{{#if showElement}}
<div ($beforeremove)="*animations.customFadeOut" />
{{/if}}
can-stache-animate
provides a jQuery extension for a bit of extra functionality. It adds the ability to provide an object for each of the before
, run
, and after
methods of an animation.
Provide an object to an animation property, and it will be treated as a css object. For before
and after
, $.fn.css
will be used, and for run
, $.fn.animate
will be used.
Example:
import canStacheAnimate from "can-stache-animate/can-stache-animate-jquery";
canStacheAnimate.registerAnimation( "blueRed", {
//background will be set to blue via $.fn.css
before: {
"background-color": "blue"
},
//blue will be animated to red
run: {
"background-color": "red"
},
after: {
"background-color": ""
}
} );