Closed shshaw closed 3 years ago
Looks like you may be describing the KeyframeEffect
class that the Animation
class uses, the base of Element.animate.
Good demos. It's not crazy the have sequencing, but the syntax need some work in terms of how it would work in Cash.
Yes.
And yeah, it probably do. See it more as an annoying sample that just shows that it is possible with very little code but still something that cuts down on the codebloat considerably.
Another thing is btw tracking state changes since it quite quickly becomes problematic to reset any changes that has been made. In the demo i needed to check my css to see what my initial states were and reset these with static values which is something that just won't work. Or have i missed something?
Btw -- a lib that is quite small, 5kb, is maybe something else to look at; snabbt.js. https://daniel-lundin.github.io/snabbt.js/
Ah, yes. I'd forgotten about Snabbt. 5kb (gzipped) is still over double the target & current weight of this, and we already have a good portion of snabbt's functionality (and a lot of other functionality snabbt doesn't have), so I think we're in a good spot :-)
Regarding state changes, a cancel
and finish
method like in the WAAPI would allow you to reset to the original state or jump to the final state. Once those methods are implemented, if you save a reference to the first animation (assuming you do multiple animations), you should be able to call cancel
to reset to that initial state even though the animation is finished ( var anim = $(el).animate(...); ... anim.cancel()
). Is that what you're referring to?
Yes, it's currently better except for the debugging stuff. :-)
For now i also think it's better to have the WAAPI-syntax (in the cases where it makes sense -- it's hardly perfect) but leave the WAAPI-specific functions out since we're still probably at least a year from WAAPI being a real option on what i would call "real sites". The browsers that has implemented it also have done so in their own specific flavour so even if function A and B works in browser C and D it isn't certain that they work the same.
Also @shshaw -- while the weight in itself is a good goal (because one have to think about the end size, not just the size of an individual lib) i think priority one may be to outline what the bare minimum is to not have something where one would use pure CSS-animations instead and to make it worthwhile to actually use the library. Here's an example where it wouldn't make sense to even include something as small as the current version of cash.animate: http://codepen.io/tommiehansen/full/RaXZzW/
What's hard to do with CSS-animations or what is hard to maintain?
beforestart()
. This is the difference between having smooth animations and having animations that stutter somewhere during its life cycle. Perf for animation is simply not a CSS vs Javascript -thing even if some libs have one believe that. Sure, in some situations JS can give 2 fps more but stuttering is pretty much always related to layout trashing of some kind.Here start/end -states comes in again but one must be vary of that things such as .getComputedStyle() triggers paints. It is extraordinarily important to not have a lib that exessively writes/reads the DOM or does so in a stupid manor (read/writes should be qued). A problem related to this is that simple demos just doesn't reveal the problems with these things since there aren't enough DOM-nodes or enough complex DOM-nodes (with styling) for it to become a visible problem. It's one thing to animate10 simple div's on a page with 0 childnodes, it's an entirely different thing to animate 2 div's on a page with 10 parent-nodes where all these have 10 childnodes and all these have 20 childnodes; all with CSS-styling etc etc.
Edit: All of these reflows/repaints/layout trashing is ofc out of the scope for the lib itself, but the lib should at least be vary of these and helpful in the way that it doesn't itself triggers bad things and gives the user some control over all the "when/what/then". :-)
I haven't forgotten about this, just been slammed at work so haven't been able to dive any deeper into it.
Thanks for the big list, @tommiehansen. I didn't recall reading it before so I'm going over it now.
Regarding 1, Sequencing/Timeline control could be accomplished in this manner, similar to what you'd written before:
$.animate([
['#myElement', { props }, { opts }],
['.element2', { props }, { opts }],
[document.getElementById('element3'), { props }, { opts }],
], { animationOpts });
animationOpts
would contain callbacks for the full animation. This could be used as default options for the keyframes, for example to set the easing for all keyframes unless overwritten by that keyframe's options.
Number 2 and number 5 are already solved by the current code, though sequencing may provide more opportunities to improve this.
Number 4 'end states' could be done with the supporting fill
property. Right now, it's handled the equivalent to fill: both
, so that if the animation is reversed, the animation keeps the values of the start state. Are you describing something different than that?
The only time getComputedStyle
is called is when the animation starts, so that the animation can be calculated. I don't see a way around that. Same with number 3, the lib will be built to not cause unnecessary paint/reflows, but if you're animating height
or other non-'safe' properties you're going to get thrashing.
Stumbled across this today: https://github.com/juliangarnier/anime
Not quite the syntax that we've been discussing and it looks like chaining/keyframing isn't really built-in, but it does have a solid feature set and weighs only 10kb minified.
@shshaw Sorry for the late replies, as you i'm often quite occupied with other things. :)
I've stumbled upon anime.js as well and while it may be good it, just as you say, lacks chaining and keyframing hence it is basically as useful as using raw CSS3-animations (greatly simplified of course) when it comes to the "create a simple animation in a short amount of time". For creating advanced animations one would probably just go with Velocity.js or TweenLite/Max from Greensock.
There's probably a lot one could borrow from anime.js though. :)
Edit: The demo i created before actually highlights sequencing in simple UI interactions http://codepen.io/tommiehansen/pen/bpXNPr
The thing is that UI elements comes more alive and becomes more natural if you add the "in-between" frames. Just click on the button in that codePen demo, what happens is all of these things:
Now imagine doing that with a system that has no sort of queing or sequencing.
Usually these things look and behave like this:
This is an unnatural flow that would never occur in nature or any sort of setting. Just imagine all the 'keyframes' required for something as simple as opening a door and walking through it:
And that's just with something as simple as opening and closing a door.
What you could do is to recreate a sequence similar to the one i did in my demo since these things is slighly hard to explain but becomes very clear once one start writing code. Just do something that has a button or something where multiple animations would be more natural. Imagine something like a light switch or any enter/exist where "enter" shows A and exit returns to default state (and possibly reverses a set of sequenced animations in some way).
I think we shouldn't add support for this if the API would be completely different from jQuery's implementation, in that case just include that other thing into your bundle along with cash.
I'm ok with including support for animation as a plugin, basically included in the repo but disabled by default in our bundles, that's basically a subset of what jQuery implements.
I think we shouldn't add support for this if the API would be completely different from jQuery's implementation, in that case just include that other thing into your bundle along with cash.
I'm ok with including support for animation as a plugin, basically included in the repo but disabled by default in our bundles, that's basically a subset of what jQuery implements.
All extras as plugins is a good way to do it.
When it comes to animation jquery isn't the gold standard to follow though.
This due to its lack of sane
sequencing, something that is discussed in this thread.
Without it one quickly becomes stuck in a callback hell of sorts.
And without sequencing there really is no need for any animation plugin.
@fabiospampinato is there any update on having animations as a plugin ?
@varunsridharan No.
@fabiospampinato ok cool.
Well i was looking to migrate my WordPress Framework away from jquery and found Cash as a best alternative.
Thanks for making a wonderful library. :-)
and i was creating a custom lib using your code and was able to add a very basic animation system and here the code.
and the code was taken from TinyAnimate with some customization
/**
* TinyAnimate.easings
* Adapted from jQuery Easing
*/
const M = Math;
const easings = {};
easings.linear = ( t, b, c, d ) => c * t / d + b;
easings.easeInQuad = ( t, b, c, d ) => c * ( t /= d ) * t + b;
easings.easeOutQuad = ( t, b, c, d ) => -c * ( t /= d ) * ( t - 2 ) + b;
easings.easeInOutQuad = ( t, b, c, d ) => ( ( t /= d / 2 ) < 1 ) ? c / 2 * t * t + b : -c / 2 * ( ( --t ) * ( t - 2 ) - 1 ) + b;
easings.easeInCubic = ( t, b, c, d ) => c * ( t /= d ) * t * t + b;
easings.easeOutCubic = ( t, b, c, d ) => c * ( ( t = t / d - 1 ) * t * t + 1 ) + b;
easings.easeInOutCubic = ( t, b, c, d ) => ( ( t /= d / 2 ) < 1 ) ? c / 2 * t * t * t + b : c / 2 * ( ( t -= 2 ) * t * t + 2 ) + b;
easings.easeInQuart = ( t, b, c, d ) => c * ( t /= d ) * t * t * t + b;
easings.easeOutQuart = ( t, b, c, d ) => -c * ( ( t = t / d - 1 ) * t * t * t - 1 ) + b;
easings.easeInOutQuart = ( t, b, c, d ) => ( ( t /= d / 2 ) < 1 ) ? c / 2 * t * t * t * t + b : -c / 2 * ( ( t -= 2 ) * t * t * t - 2 ) + b;
easings.easeInQuint = ( t, b, c, d ) => c * ( t /= d ) * t * t * t * t + b;
easings.easeOutQuint = ( t, b, c, d ) => c * ( ( t = t / d - 1 ) * t * t * t * t + 1 ) + b;
easings.easeInOutQuint = ( t, b, c, d ) => ( ( t /= d / 2 ) < 1 ) ? c / 2 * t * t * t * t * t + b : c / 2 * ( ( t -= 2 ) * t * t * t * t + 2 ) + b;
easings.easeInSine = ( t, b, c, d ) => -c * M.cos( t / d * ( M.PI / 2 ) ) + c + b;
easings.easeOutSine = ( t, b, c, d ) => c * M.sin( t / d * ( M.PI / 2 ) ) + b;
easings.easeInOutSine = ( t, b, c, d ) => -c / 2 * ( M.cos( M.PI * t / d ) - 1 ) + b;
easings.easeInExpo = ( t, b, c, d ) => ( t == 0 ) ? b : c * M.pow( 2, 10 * ( t / d - 1 ) ) + b;
easings.easeOutExpo = ( t, b, c, d ) => ( t == d ) ? b + c : c * ( -M.pow( 2, -10 * t / d ) + 1 ) + b;
easings.easeInOutExpo = ( t, b, c, d ) => {
if( t == 0 ) {
return b;
}
if( t == d ) {
return b + c;
}
return ( ( t /= d / 2 ) < 1 ) ? c / 2 * M.pow( 2, 10 * ( t - 1 ) ) + b : c / 2 * ( -M.pow( 2, -10 * --t ) + 2 ) + b;
};
easings.easeInCirc = ( t, b, c, d ) => -c * ( M.sqrt( 1 - ( t /= d ) * t ) - 1 ) + b;
easings.easeOutCirc = ( t, b, c, d ) => c * M.sqrt( 1 - ( t = t / d - 1 ) * t ) + b;
easings.easeInOutCirc = ( t, b, c, d ) => ( ( t /= d / 2 ) < 1 ) ? -c / 2 * ( M.sqrt( 1 - t * t ) - 1 ) + b : c / 2 * ( M.sqrt( 1 - ( t -= 2 ) * t ) + 1 ) + b;
easings.easeInElastic = ( t, b, c, d ) => {
var p = 0;
var a = c;
if( t == 0 ) {
return b;
}
if( ( t /= d ) == 1 ) {
return b + c;
}
if( !p ) p = d * .3;
if( a < M.abs( c ) ) {
a = c;
var s = p / 4;
} else {
var s = p / ( 2 * M.PI ) * M.asin( c / a );
}
return -( a * M.pow( 2, 10 * ( t -= 1 ) ) * M.sin( ( t * d - s ) * ( 2 * M.PI ) / p ) ) + b;
};
easings.easeOutElastic = ( t, b, c, d ) => {
var p = 0;
var a = c;
if( t == 0 ) {
return b;
}
if( ( t /= d ) == 1 ) {
return b + c;
}
if( !p ) p = d * .3;
if( a < M.abs( c ) ) {
a = c;
var s = p / 4;
} else {
var s = p / ( 2 * M.PI ) * M.asin( c / a );
}
return a * M.pow( 2, -10 * t ) * M.sin( ( t * d - s ) * ( 2 * M.PI ) / p ) + c + b;
};
easings.easeInOutElastic = ( t, b, c, d ) => {
var p = 0;
var a = c;
if( t == 0 ) {
return b;
}
if( ( t /= d / 2 ) == 2 ) {
return b + c;
}
if( !p ) p = d * ( .3 * 1.5 );
if( a < M.abs( c ) ) {
a = c;
var s = p / 4;
} else {
var s = p / ( 2 * M.PI ) * M.asin( c / a );
}
if( t < 1 ) {
return -.5 * ( a * M.pow( 2, 10 * ( t -= 1 ) ) * M.sin( ( t * d - s ) * ( 2 * M.PI ) / p ) ) + b;
}
return a * M.pow( 2, -10 * ( t -= 1 ) ) * M.sin( ( t * d - s ) * ( 2 * M.PI ) / p ) * .5 + c + b;
};
easings.easeInBack = ( t, b, c, d, s ) => {
if( isUndefined( s ) ) {
s = 1.70158;
}
return c * ( t /= d ) * t * ( ( s + 1 ) * t - s ) + b;
};
easings.easeOutBack = ( t, b, c, d, s ) => {
if( isUndefined( s ) ) {
s = 1.70158;
}
return c * ( ( t = t / d - 1 ) * t * ( ( s + 1 ) * t + s ) + 1 ) + b;
};
easings.easeInOutBack = ( t, b, c, d, s ) => {
if( isUndefined( s ) ) {
s = 1.70158;
}
return ( ( t /= d / 2 ) < 1 ) ? c / 2 * ( t * t * ( ( ( s *= ( 1.525 ) ) + 1 ) * t - s ) ) + b : c / 2 * ( ( t -= 2 ) * t * ( ( ( s *= ( 1.525 ) ) + 1 ) * t + s ) + 2 ) + b;
};
easings.easeInBounce = ( t, b, c, d ) => {
return c - easings.easeOutBounce( d - t, 0, c, d ) + b;
};
easings.easeOutBounce = ( t, b, c, d ) => {
if( ( t /= d ) < ( 1 / 2.75 ) ) {
return c * ( 7.5625 * t * t ) + b;
} else if( t < ( 2 / 2.75 ) ) {
return c * ( 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + .75 ) + b;
} else if( t < ( 2.5 / 2.75 ) ) {
return c * ( 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + .9375 ) + b;
} else {
return c * ( 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + .984375 ) + b;
}
};
easings.easeInOutBounce = ( t, b, c, d ) => ( t < d / 2 ) ? easings.easeInBounce( t * 2, 0, c, d ) * .5 + b : easings.easeOutBounce( t * 2 - d, 0, c, d ) * .5 + c * .5 + b;
const animation = function( from, to, duration, update, easing, done ) {
if( !isNumber( from ) || !isNumber( to ) || !isNumber( duration ) || !isFunction( update ) ) {
return 'OOO';
}
if( isString( easing ) && easings[ easing ] ) {
easing = easings[ easing ];
}
if( !isFunction( easing ) ) {
easing = window[ easing ] || null;
}
if( !isFunction( easing ) ) {
easing = easings.linear;
}
if( !isFunction( done ) ) {
done = function() {
};
}
let canceled = false,
change = to - from,
rAF = v.win.requestAnimationFrame || ( ( callback ) => v.win.setTimeout( callback, 1000 / 60 ) ),
loop = function( timestamp ) {
if( canceled ) {
return;
}
var time = ( timestamp || +new Date() ) - start;
if( time >= 0 ) {
update( easing( time, from, change, duration ) );
}
if( time >= 0 && time >= duration ) {
update( to );
done();
} else {
rAF( loop );
}
};
update( from );
var start = v.win.performance && v.win.performance.now ? v.win.performance.now() : +new Date();
rAF( loop );
return { cancel: () => canceled = true, };
};
animation.animateCSS = function( element, property, from, to, duration, easing, done ) {
let existing = regex.cssProperty.exec( core( element ).css( property ) ),
animateTo = regex.cssProperty.exec( to ),
toNumber = ( isUndefined( animateTo[ 1 ] ) ) ? to : animateTo[ 1 ],
toUnit = ( isUndefined( animateTo[ 2 ] ) ) ? existing[ 2 ] : animateTo[ 2 ];
from = ( isNull( from ) ) ? existing[ 1 ] : from;
let update = function( value ) {
return element.style[ property ] = value + toUnit;
};
from = parseFloat( from ) || 0;
toNumber = parseFloat( toNumber ) || 0;
duration = parseInt( duration ) || 0;
return animation( from, toNumber, duration, update, easing, done );
};
animation.cancel = function( instance ) {
if( !instance ) {
return;
}
instance.cancel();
};
fn.animation = function( property, to, duration, easing, callback ) {
return this.each( ( i, elem ) => {
animation.animateCSS( elem, property, null, to, duration, easing, callback );
} );
};
Closing as an official animation plugin won't be written, use one of the suggested ones. If anybody knows of any plugin that's compatible with Cash and implements the same API as jQuery well I'll link to it in the readme.
Latest Prototype
Cash.fn.animate essentials:
Element.animate
, falling back to arequestAnimationFrame
animation engine$.animate
method for animating object properties using therequestAnimationFrame
engine.requestAnimationFrame
loop for best performancetransform
properties usingx
,y
,z
,scale
, etc. (we should be able to parse the currentelement.style.transform
to fill in the 'from' values.)start
andcomplete
callbacks (per element)progress
callbacks (may be problematic withElement.animate
, but necessary for Object callbacks)width: 100%
towidth: 20px
, for example. This is necessary for the requestAnimationFrame fallback, but WAAPI handles it automatically)Possible features:
collectionComplete
callback (allComplete
,animationend
?)fill
support? Currently applies whatever the end state of animation is (equivalent tofill: both
)player.pause(); //"paused"
,player.play(); //"running"
,player.cancel(); //"idle"... jump to original state
andplayer.finish(); //"finished"...jump to end state
,player.reverse()
)var frames = [ [{ props }, { opts }], [{ props }, { opts }] ]; $(element).animate(frames);
Original Issue
I think the future of Cash may need to center around modularity with lightweight extensions/plugins. There are only so many DOM methods we can add that are worthwhile, and performance optimizations will only go so far.
The main Cash package will always have the most useful methods to help beginners get started, but I picture developers being able to select the features/modules they need to get a lightweight package (less than 10-15kb) that gives them exactly what they need for a project without any cruft like we've talked about in #94.
Thinking along those terms, I have a basic animation engine (two files: animateCore.js and canvallax.Animate.js that's quite performant. It could easily be adapted as a cash plugin, minified to about 1kb gzipped, 2kb minified.
Currently it's more along the lines of GSAP TweenMax syntax and designed to animate properties of objects, but it could be adapted to handle CSS properties and more closely match the jQuery syntax for compatibility while introducing some more advanced functions (delay, repeat, animateFrom, animateFromTo, etc).
Like the discussion for AJAX ( #103 ), this wouldn't be an addition to the core, but a separate module for those that want a lightweight but familiar syntax to run simple animations. jQuery with the animation module is at least 50kb, and TweenLite + jQuery support is around 30kb. That's a lot to add for simple effects like spinning an element when clicked or to smoothing a transition from point A to point B.
Any thoughts on animation or the future of Cash?