Closed mickmcgrath13 closed 7 years ago
Here's a branch implementing the above request: https://github.com/canjs/can-animate/compare/advanced-options?expand=1
Things to note about advanced options (also [rather poorly] documented here)
Specify an options object on the scope.
<div can-animation-options="{options}"></div>
options
object{
duration: 1000, //the global duration for this set of animations
bindings: {
"inserted": "fade-in",
"removed": "fade-out",
//function - called when prop1 changes
// call js animation from within this function
"prop1": function(props){},
//object - run, before, and after props
"prop2": {
//function - main animation function - called when prop2 changes
// call js animation from within this function
"run": function(props),
//function - can-animate calls this before "run"
"before": function(props){},
//function - can-animate calls this when the animation inside of "run" is done
"after": function(props){}
},
//plugins like these not yet supported
"prop3": "custom-animation",
//run
"prop4": {
"run": "custom-animation", //plugins like these not yet supported
"after": function(props){}
},
//specify css properties only (not yet supported)
prop5: {
//set up the element
before:{
"opacity":0,
"top":"-100px",
"z-index":"1000000000"
},
//animate to this
run:{
"opacity":1,
"top":"0px"
},
//finalize the properties
after:{
"z-index":""
}
}
}
}
Fade In on inserted:
var scope = new can.Map({
animateOptions: {
"bindings":{
"inserted": {
"run": "fade-in"
}
}
}
});
Fade Out on removed:
var scope = new can.Map({
animateOptions: {
"bindings":{
"removed": {
"run": "fade-out"
}
}
}
});
Custom animation on inserted:
var scope = new can.Map({
animateOptions: {
"bindings":{
"inserted": function(opts){
$(opts.el)
.css({
opacity:0,
top:"-100px"
})
.animate({
"opacity":1,
"top":"0px"
}, 1000, function(){
console.log("inserted done");
});
}
}
}
});
Custom animation on inserted with callbacks:
var scope = new can.Map({
animateOptions: {
"bindings":{
"inserted": {
"run": function(opts){
console.log("inserted - running");
$(opts.el).hide().fadeIn(opts.options.duration, function(){
console.log("inserted - animation done");
});
},
"before": function(){
//can-animate will call this before 'run'
console.log("inserted - before");
},
"after": function(){
//can-animate will call this when run is complete
console.log("inserted - after");
}
},
}
}
});
In addition to the inserted
and removed
events, animations can be attached to any property on the scope. Here's a function that will run when hasError
changes.
"hasError": function(opts){
if(opts.context.attr("hasError")){
console.log("run animation");
$(opts.el).fadeOut(200, function(){
$(opts.el).fadeIn(1000, function(){
console.log("hasError animation done");
});
});
}else{
console.log("don't run animation");
}
}
before
and after
methods work here, too:
"hasError": {
"run": function(opts){
if(opts.context.attr("hasError")){
$(opts.el).fadeOut(opts.options.duration, function(){
$(opts.el).fadeIn(opts.options.duration, function(){
console.log("hasError animation done");
});
});
}else{
console.log("don't run animation");
}
},
before: function(opts){
if(!opts.context.attr("hasError")){
return false; //return false to cancel the run method
}
//animation is good to go, set up some things
$(opts.el).css({
//starting styles
})
},
after: function(){
console.log("hasError after", arguments);
//animation is good to go, set up some things
$(opts.el).css({
//final styles
})
},
"duration": 1500 //can override the duration from root of animate options
}
Fade In on inserted:
var scope = new can.Map({
animateOptions: {
"bindings":{
"inserted": "fade-in"
}
}
});
Fade Out on removed:
var scope = new can.Map({
animateOptions: {
"bindings":{
"removed": "fade-out"
}
}
});
Custom animation when property is true:
var scope = new can.Map({
animateOptions: {
"bindings":{
"hasError": "shake"
}
}
});
Specify css object instead of function for before, after, and run
var scope = new can.Map({
animateOptions: {
"bindings":{
"isActive": {
//set up the element
before:{
"position":"relative",
"left":"0px",
"cursor": "wait"
},
//animate to this
run:{
"left":"10px"
},
//finalize the properties
after:{
"cursor":""
}
}
}
}
});
First: though binding
is better than whens
, if you still don't like bindings
(multiple meanings across js and can), how about hooks
?
Second: I far prefer this to current can-animate attributes, perhaps instead of can-animate-options="{whatev}"
it could just be can-animate="{whatev}"
?
Third: I think it was mentioned before but you may need a cancelled
binding (hook?) and perhaps some way to cancel animations that don't have that binding (like a default cancel) for when the occasion calls to interrupt an animation.
I don't know enough about the animation space to know if it's a robust solution, but it seems easy to reason about and covers most things I could think of out of the box, so I like this idea/branch.
Thanks @BigAB. I've made some changes (including changing bindings
to hooks
and changing can-animate-options
to just can-animate
).
I do think i'd still like to support the following in the future:
can-animate="fade"
can-animate-in="fade"
can-animate-out="fade"
can-animate-duration="2000"
Ideally, all of these will be supported:
(for can-animate="animateOptions"
)
animateOptions: "fade"
animateOptions: {
hooks: {
"inserted": "fade"
}
}
animateOptions: {
hooks: {
"inserted": "myCustomAnimationBehavior"
}
}
animateOptions: {
"hooks": {
"inserted": {
"opacity":1
}
}
}
animateOptions: {
"hooks":{
"inserted": {
//set up the element
before:{
"position":"relative",
"left":"0px",
"cursor": "wait"
},
//animate to this
run:{
"left":"10px"
},
//finalize the properties
after:{
"cursor":""
}
}
}
}
Provide some way of cancelling animations
Updated proposal here: https://github.com/canjs/can-animate/blob/advanced-options/README.md
Feedback welcome
Instead of using the attribute name for arguments why not use the attribute value, especially since can-stache has a lot of functionality already built to parse and convert to values?
<my-component can-animate="duration: 100, on: something" />
Or immediately available:
<my-component can-animate="duration=100 on=something type=fade" />
I think that works for a single animation, but what if I want a different animation for each of several different property changes or even different animations for different values of a single property? With the attribute name method, it can easily be done:
<my-component can-animate-myProp!="some-animation-for-truthy" can-animate-!myProp="some-animation-for-falsey" />
With the examples you've given, I can't clearly see how to accomplish that.
Taking the original example problem at the start of this issue:
/* Problem:
* Consider a simple input modal which has
* a message, a text input, a cancel button, and a confirm button.
* ------------------------------------------
* | Type a number |
* | --------------------- |
* | | | |
* | --------------------- |
* | |
* | [ Cancel ] [ Confirm ] |
* ------------------------------------------
*
* The user wants the following for the modal:
* Scale in (custom animation) when inserted
* Fade out when removed
* Shake when there is an error (scope.attr('hasError', true))
*/
With the proposal I gave, I could do:
<number-input-modal can-animate="animateOptions" can-animate-$inserted="scale-in" can-animate-$removed="fade-out" can-animate-hasError!="shake" />
How would I do that with the attribute value? The first thing that comes to mind would be something like:
<number-input-modal can-animate="opts=animateOptions $inserted=scale-in $removed=fade-out hasError!=shake" />
in which case I'd actually prefer to just do:
<number-input-modal can-animate="animateOptions" />
..and just define it all in that object like this.
New concept based on offline discussions:
Provide a left-hand-side scope property to bind to, and simply call a method for the animation.
<can-import from="can-animate" {^value}="animate" />
<my-component
{child-prop}="scopeProp"
(. scopeEvent)="animate.someAnimationMethod"
(.. parentScopeEvent)="animate.someParentAnimateMethod"
{. scopeProp}="animate.someScopeAnimationMethod"
($inserted)="animate.fade scopeUserProp.patience"
($onInsertedAnimationFinished)="someScopeMethod(%event)"
/>
<can-import from="can-animate" {^value}="animate" />
-> import can-animate into the scope which will have predefined animations like fade
, etc
<can-import from "./animate-methods" {^value}="animate" />
. ./animate-options.js
would import can-animate and modify things as needed{child-prop}="scopeProp"
-> standard way of binding still works(. scopeEvent)="animate.someAnimationMethod"
-> when child
's scopeEvent
fires, run animate.someAnimationMethod
(.. parentScopeEvent)="animate.someParentAnimateMethod"
-> when child
's parent's scope event, run animate.someParentAnimateMethod
($inserted)="animate.fade scopeUserProp.patience"
-> run animate.fade
when the dom event inserted
is triggered and pass it a value of scopeUserProp.patience
($onInsertedAnimationFinished)="someScopeMethod(%event)"
-> bind to an event triggered by one of the animationsAnimations are a set of before
, run
, and after
methods. How would we take advantage of that given the following syntax:
<child
(. hasError)="animate.pulseRed"
/>
if pulseRed is something like this:
"pulseRed": {
before: function(opts){
//set up some css properties on opts.$el
},
run: function(opts){
//pulse opts.$el's background-color to red, then to white
},
after: function(opts){
//reset opts.$el's css props to their original values
}
}
..we could do something like this?
(. hasError)="animate.run('pulseRed')"
What would happen if we wanted to call a viewmodel method and an animation when a dom event happens? For example:
<child
($click)="animate.pulseGreen"
($click)="someVmMethodLikeLogClickToServer()"
/>
..maybe semicolons are necessary?
<child
($click)="animate.pulseGreen; someVmMethodLikeLogClickToServer()"
/>
How best to bind to events triggered by one of the used animations?
($onInsertedAnimationFinished)="someScopeMethod(%event)" For example, what if multiple animations trigger the same event name (
onComplete` for example)? How to specify which one to listen to?
<child
(. hasError)="animate.pulseRed"
(. notHasError)="animate.pulseGreen"
($onComplete.pulse-red)="callWhenHasErrorAnimationDone()"
/>
..could we do something like this:
(. hasError)="animate.pulseRed().then(./whenHasErrorAnimationIsDone())"
Not really designer friendly, though
()
on rhs change the parameters?Are the parameters given to pulseRed
different for this:
(. hasError)="animate.pulseRed"
..than for this:
(. hasError)="animate.pulseRed()"
What if we want to bind to a scope prop's property but the scope property itself is dynamic? We'd need to re-bind to the scope property when it changes. For example:
<child
(../dynamicScopeProperty prop)="animate.someAnimation"
/>
if (. hasError)="animate.pulseRed"
runs pulseRed
when hasError
is true, how would we run an animation when, for example, searchTerm
's value changes?
<child
(. searchTerm)="animate.wiggle"
/>
..what if we assume that the right-hand-side always runs on change, and then can-animate provides some helper methods to specify whether to run on truthy or falsey?
(. scopeProp)="animate.onTrue(animate.fade)"
..but does that mean that we always have to pass %element
and/or %event
?
(. scopeProp)="animate.onTrue(%element, %event, animate.fade)"
Suppose someone sets up an animation on an input event:
<input
{child-prop}="scopeProp"
(. scopeProp)="animate.fade"
(.. noLongerHasAnError)="animate.something" />
...then later on, they create a custom input component. ..they would need to change the scopes
<custom-input
{child-prop}="scopeProp"
(.. scopeProp)="animate.fade"
(../.. noLongerHasAnError)="animate.something" />
IMO, the stache-based syntax is much less designer friendly. Doing this:
(. hasError)="animate.pulseRed"
means that designers need to have an understanding of scope (.
, ..
, etc) as well as "what happens if I add ()
, etc.
..as opposed to simply define your animations in a file, import it with stache, and use the stache imported object on the element
I would prefer to move forward with what I have (can-animate="animateOptions"
) and then work on solving some of the above issues and better integrating into things like can-stache-bindings
and such (maybe then move from can-animate
to can-stache-animate
?)
This has resulted in https://www.npmjs.com/package/can-stache-animate.
This issue can be closed
I'd like to see some way of specifying multiple types of custom animations per element. I've had the following idea for a while, but I haven't put any thought into it for several months. I thought I'd drop a proposal to get a conversation started: