fast-animation-tool
v0.6.8
Published
The fast animation tool with zero dependencies.
Downloads
7
Maintainers
Readme
Web's fastest and most lightweight animation tool.
When it comes to raw animation speed FAT outperforms every single web animation library out there and also provides flexible animation capabilities like scenes, sequences, transforms, coloring, controlling and easing.
Installation Guide • API Reference • Examples • Custom Builds • Benchmark Ranking
Get Latest (Stable Release):
All Features:
The flags DEBUG and PROFILER are also available in fat.js for non-production use.
It is also very simple to make a Custom Build
Benchmark Ranking
Library Comparison: Benchmark "Bouncing Balls"
"Animate" (2000 Bouncing Balls)
"Transforms" (2000 Bouncing Balls)
"Colors" (2000 Flashing Balls)
Browser: Chrome (Desktop), Test Duration: 30 sec (median value) * Memory Heap: The size of memory the animations requires to execute ** Memory Allocation: The amount of memory which was allocated during animation runtime
Library Comparison: Benchmark "Bouncing Balls"
Installation
HTML / Javascript
<html>
<head>
<script src="fat.min.js"></script>
</head>
...
Note: Use fat.min.js for production and fat.js for development.
Use latest stable release from CDN:
<script src="https://cdn.jsdelivr.net/gh/nextapps-de/fat@master/fat.min.js"></script>
Use a specific version from CDN:
<script src="https://cdn.jsdelivr.net/gh/nextapps-de/[email protected]/fat.min.js"></script>
Common JS
In your code include as follows:
var Fat = require("Fat");
AMD
var Fat = require("./fat.min.js");
API Overview
The namespace "Fat" is also the default scene (global scene).
Global methods / Scene methods:
- Fat.animate(selector | elements, styles | preset, options, callback)
- Fat.transform(selector | elements, styles, options, callback)
- Fat.filter(selector | elements, styles, options, callback)
- Fat.transition(selector | elements, styles, options, callback)
- Fat.native(selector | elements, styles, options, callback)
- Fat.destroy()
Control methods:
- Scene.set(selector | elements, styles, force?)
- Scene.set(selector | elements, style, value, force?)
- Scene.remove(selector | elements, styles)
- Scene.pause(boolean: toggle)
- Scene.reverse(boolean: toggle)
- Scene.start(boolean: toggle)
- Scene.finish()
- Scene.reset()
- Scene.loop(int: count)
- Scene.shift(int: ms)
- Scene.seek(float: position)
- Scene.speed(float: ratio)
Scene Options
Animation Options
Basic Usage
Fat.animate(selector[] | elements[], styles[]{}, options{}, callback)
Fat.animate("#mydiv", { left: "100px" },{ /* options */ });
Pass in an element, an array of elements or a dom query selector.
Fat.animate("#mydiv", {
left: "100px",
top: "100px"
},{
delay: 1000,
duration: 2000,
ease: "easeInOut",
callback: function(){
// "this" refers to #mydiv
console.log(this.style.left);
}
});
See all available options above.
Pass in custom options for each style property:
Fat.animate("#mydiv", {
left: {
from: 0,
to: 100,
unit: "%",
duration: 2000,
ease: "linear"
},
top: {
from: 0,
to: "100%",
duration: 2000,
ease: "quadIn",
delay: 2000
}
});
Passing a unit parameter is slightly faster.
"From-To-Unit" Shortcut property: [from, to, unit]
:
Fat.animate("#mydiv", {
left: [0, 100, "%"], // from 0% to 100%
top: [0, "100%"],
});
Alternatively pass the callback function as the last parameter:
Fat.animate("#mydiv", {
left: "100px",
top: "100px"
},{
delay: 2000,
duration: 2000,
ease: "easeInOut"
}, function(){
// done
});
Fat.animate("#mydiv", { top: "100px" }, function(){
// done
});
Fat.animate("#mydiv", "slideInTop", function(){
// done
});
Delay an animation until the target element comes into view (e.g. by scrolling):
Fat.animate("#mydiv", { top: "100px" }, { delay: "view" });
Relative Values:
Calculate values depending on the current state:
// current left + 100px
Fat.animate("#mydiv", { left: "+=100px" });
// double of current top
Fat.animate("#mydiv", { top: "*=2" });
// current left - 100px
Fat.animate("#mydiv", { left: "-=100px", });
// half of current top
Fat.animate("#mydiv", { top: "/=2" });
Toggle values depending on the current state:
// toggle current left (100% or 0%)
Fat.animate("#mydiv", { left: "!=100%" });
Transform
Separate notation provides the best performance:
Fat.animate("#mydiv", {
translateX: "100px",
translateY: "100px"
});
same as:
Fat.transform("#mydiv", { ... });
alternatively:
Fat.animate("#mydiv", {
"transform": "translate(100px, 100px)"
});
same as:
Fat.transform("#mydiv", "translate(100px, 100px)");
Colors
Fat.animate("#mydiv", {
color: "#f00",
backgroundColor: "rgba(0, 255, 0, 1)",
borderColor: "hsla(0, 100%, 100%, 1)"
});
Separate notation provides the best performance:
Fat.animate("#mydiv", {
colorR: 0,
colorG: 0,
colorB: 0,
colorA: 0,
backgroundColorA: "100%",
borderColorB: 255
});
Filter
Separate notation provides the best performance:
Fat.animate("#mydiv", {
brightness: 0.5,
contrast: 0.5,
hue: "180deg"
});
You can use the shorthand
hue
ashue-rotate
same as:
Fat.filter("#mydiv", { ... });
alternatively:
Fat.animate("#mydiv", {
"filter": "brightness(0.5) contrast(0.5) hue(180deg)"
});
same as:
Fat.filter("#mydiv", "brightness(0.5) contrast(0.5) hue(180deg)");
Easing
Built-in easing:
- linear
- easeIn, easeOut, easeInOut (refers to: quadIn, quadOut, quadInOut)
- cubicIn, cubicOut, cubicInOut
- quartIn, quartOut, quartInOut
- quintIn, quintOut, quintInOut
- sineIn, sineOut, sineInOut
- expoIn, expoOut, expoInOut
- circIn, circOut, circInOut
- backIn, backOut, backInOut
- snap
Static (Pre-Cached) vs. Dynamic Easing
There are two ways to define easing functions. When your easing is a static curve (like easeIn, backInOut, elastic, etc.) you should define the easing via Fat.ease["myEasing"] = fn()
and simply pass the name as string within the Fat.animate
options. This will prefetch all the calculations, so you are free to use really heavy easing definitions without any performance drawbacks.
When you want to use dynamic easing, which depends on runtime calculations, you should pass the easing function directly to the Fat.animate
options. In this case the easing calculation will not prefetch. This allows you to control easing programmatically and adding logic during runtime.
Define custom static easing function (1-parameter style):
Fat.ease["linear"] = function(x){
return x;
};
x: current progress (0.0 - 1.0)
Define custom static easing function (4-parameter style):
Fat.ease["linear"] = function(t, b, c, d){
return b + (c - b) * (t / d);
};
t: current time, b: from value, c: to value, d: duration
Apply the custom static easing:
Fat.animate("#mydiv", { left: "100px" },{ ease: "linear" });
Use cubic bezier:
Fat.animate("#mydiv", { left: "100px" },{ ease: "cubic(0, 1, 0, 1)" });
Shorthand array notation for a bezier is recommended:
... ,{ ease: [0, 1, 0, 1] });
Define custom dynamic easing function (1-parameter style):
Fat.animate("#mydiv", { left: "100px" },{ ease: function(x){
// doing some crazy calculations depends on runtime
return x;
}});
Define custom dynamic easing function (4-parameter style):
Fat.animate("#mydiv", { left: "100px" },{ ease: function(t, b, c, d){
// doing some crazy calculations depends on runtime
return x;
}});
Custom Property / Renderer
Fat.animate(custom_object[]{}, custom_property[]{}, options{})
Note: You can't use more than one custom property per animation on a HTML element. Instead when animating custom object properties there are no limits.
Just add a property with the name "custom":
Fat.animate("#mydiv", {
custom: "50%"
},{
ease: "cubicInOut",
step: function(progress, current){
this.style.left = current;
}
});
Handle unit separately:
Fat.animate("#mydiv", {
custom: 50
},{
ease: "cubicInOut",
step: function(progress, current){
this.style.left = current + "%";
}
});
Pass in custom object/function as first parameter instead of an element:
Fat.animate({
obj: document.getElementById("mydiv")
},{
custom: 50
},{
ease: "cubicInOut",
step: function(progress, current){
// "this" refers to the custom object
this.obj.style.left = current + "%";
}
});
You can also use sequences:
... [custom: 50, custom: 0, custom: 100, custom: 0]
This way it is possible to pass custom data, logic and renderer through each animation job, e.g.:
var handler = {
unit: "%",
obj: document.getElementById("mydiv"),
set: function(property, value){
this.obj.style[property] = value + this.unit;
}
};
Fat.animate(handler, { custom: 50 },{
ease: "cubicInOut",
step: function(progress, current){
// "this" refers to handler
this.set("left", current);
}
});
You can also use array of objects/handlers:
Fat.animate([handler1, handler2, handler3], ...
If you don't need the from/to transition values at all, another scenario could be:
function cubicInOut(x) {
return ((x *= 2) <= 1 ? x*x*x : (x -= 2) *x*x + 2) / 2;
}
Fat.animate({ ease: cubicInOut },{ custom: true },{
step: function(progress){
var current = this.ease(progress);
// console.log(current);
}
});
alternatively:
Fat.animate({},{ custom: true },{ step: function(progress){
var current = cubicInOut(progress);
// console.log(current);
}});
or:
Fat.animate({},{ custom: 1 },{
ease: cubicInOut,
step: function(progress, current){
// console.log(current);
}
});
Tween custom object properties:
function draw(){
this.obj.style[this.property] = this.value;
}
var custom = {
value: 0,
property: "left",
obj: document.getElementById("#mydiv")
};
Fat.animate(custom, { value: "50%" },{
duration: 2000,
ease: "cubicInOut",
step: draw
});
Sequences
Fat.animate("#mydiv", [
{ left: "100%" }, // 1st animation, 2000ms
{ top: "100%" }, // 2nd animation, 2000ms
{ left: 0 }, // 3rd animation, 2000ms
{ top: 0 } // 4th animation, 2000ms
],{
callback: function(){ alert("Next Loop") },
delay: 2000,
loop: -1 // infinite
});
Use custom options per style property:
Fat.animate("#mydiv", [{
left: { // 1st animation
from: 0,
to: 100,
unit: "%",
duration: 2000
}
},{
top: { // 2nd animation
to: "100%",
duration: 2000,
ease: "easeInOut",
delay: 0
}
},
...
Keyframes
Fat.animate("#mydiv", {
"25%": { left: "100%" }, // 0 -> 25%, 500ms
"50%": { top: "100%" }, // 25 -> 50%, 500ms
"75%": { left: 0 }, // 50 -> 75%, 500ms
"100%": { top: 0 } // 75 -> 100%, 500ms
},{
callback: function(){ alert("Next Loop") },
delay: 2000,
loop: -1 // infinite
});
Use custom options per style property:
Fat.animate("#mydiv", {
"0%": {
left: {
to: "100%",
ease: "easeIn"
}
},
"100%": {
top: {
to: "0%",
ease: "easeOut"
}
}
},
...
Presets (Effects)
Fat.animate("#mydiv", "fadeOut");
Combine multiple presets (ordered):
Fat.animate("#mydiv", "fadeOut zoomOut rollOutRight");
Also usable with sequences:
Fat.animate("#mydiv", ["slideInTop", "zoomIn"]);
Define custom preset:
Fat.preset["fade-out-down"] = {
opacity: 0,
translateY: "100%"
};
Use custom preset:
Fat.animate("#mydiv", "fade-out-down");
Builtin Presets:
- fadeIn, fadeOut, fadeToggle
- slideInLeft, slideOutLeft, slideToggleLeft
- slideInRight, slideOutRight, slideToggleRight
- slideInTop, slideOutTop, slideToggleTop
- slideInBottom, slideOutBottom, slideToggleBottom
- zoomIn, zoomOut, zoomToggle
- rollOutRight (clockwise), rollInRight, rollToggleRight
- rollOutLeft (opposite), rollInLeft, rollToggleLeft
- blurIn, blurOut, blurToggle
- scrollUp, scrollDown, scrollLeft, scrollRight
Scenes (Groups)
Get the global scene (default):
var scene = Fat.animate(element, { left: "100%" });
Create a new scene:
var scene = Fat.create();
Add animations to a scene:
scene.animate(element, { left: "100%" });
Destroy scene:
scene.destroy();
Useful Example
Considering the following example:
var scene_1 = Fat.animate(element_1, { left: "100%" });
var scene_2 = Fat.animate(element_2, { left: "100%" });
var scene_3 = Fat.animate(element_3, { left: "100%" });
// this will also destroy scene_2 and scene_3:
scene_1.destroy();
All variables points to the same global scene on which is "Fat" basically based on.
This is the correct workaround:
var scene_1 = Fat.create().animate(element_1, { left: "100%" });
var scene_2 = Fat.create().animate(element_2, { left: "100%" });
var scene_3 = Fat.create().animate(element_3, { left: "100%" });
// this will just destroy scene_1:
scene_1.destroy();
Do not massively create new scenes and also do not create them by default. A large amount of parallel scenes results in a performance drawback.
Controls
Fat internally points to default global scene, so you can use all scene methods on Fat accordingly.
Update single style:
scene.set("#mydiv", "left", "0%");
Update multiple styles:
scene.set("#mydiv", { top: 0, left: 0 });
Remove all animations from an object:
scene.remove("#mydiv");
Remove a specific animation from an object:
scene.remove("#mydiv", "left");
Remove a list of specific animations from an object:
scene.remove("#mydiv", ["top", "left"]);
Pause a scene:
scene.pause();
alternatively:
scene.start(false);
Play a scene:
scene.start();
alternatively:
scene.pause(false);
Revert playback (toggle):
scene.reverse();
alternatively set direction:
scene.reverse(false);
Reset playback state and jump back to the start:
scene.reset();
Finish and also execute callback:
scene.finish();
Set playback speed:
scene.speed(0.5); // half
scene.speed(1); // normal
scene.speed(2); // double
scene.speed(-2); // double (reversed direction)
Seek a scene to a specific position:
scene.seek(0); // start
scene.seek(0.5); // middle
scene.seek(1); // end
Shift a scene relative to the current position (by milliseconds):
scene.shift(2000); // current + 2000 ms
scene.shift(-500); // current - 500 ms
Looping Sequences & Reversed Direction
When looping sequences and also have reversed animation direction (e.g. by setting speed < 0) you have to pass a from-to declaration pair for each style, otherwise the from-value gets lost when looping back from reversed direction.
var scene = Fat.animate(element, [{
left: { from: "0%", to: "50%" }
},{
left: { from: "50%", to: "0%" }
}],{
loop: -1
});
scene.reverse();
alternatively use from-to-unit shorthand:
var scene = Fat.animate(element, [{
left: [0, 50, "%"]
},{
left: [50, 0, "%"]
}],{
loop: -1
});
scene.reverse();
alternatively use relative toggle:
var scene = Fat.animate(element, [{
left: "!=50%"
},{
left: "!=0%"
}],{
loop: -1
});
scene.reverse();
Scroll
Scroll document/element to a specific position (vertically):
Fat.animate(element, { scrollTop: 500 });
Scroll horizontally:
Fat.animate(element, { scrollLeft: 500 });
Scroll in both directions scroll: [x, y]
:
Fat.animate(element, { scroll: [500, 500] });
Use relative values:
Fat.animate(element, { scroll: "+=50" });
Paint
Schedule a task to perform during next animation frame:
Fat.paint(function(time){
console.log("Now: " + time);
});
Schedule a task with a delay and keep the paint id:
var id = Fat.paint(function(time){
console.log("Now: " + time);
}, 2000);
Remove the above scheduled task from the queue:
Fat.cancel(id);
Loop a task with every animation frame:
Fat.paint(function(time){
console.log("Now: " + time);
return true;
});
Just return true to keep the loop alive. Return false or return nothing to break the loop.
Init (Options)
Considering the following example:
Fat.animate(element, { top: "100%" }, function(){
this.style.top = 0; // this style change will be shadowed
Fat.animate(this, { top: "100%" });
});
This is called animation loop, the callback creates a new animation on the same objects style property. Technically the callback executes during the last frame of the first animation. So there is still running an animation on this property and will be inherited by the next animation loop.
During the callback, external changes on the same style property which is going to be animated will be shadowed by the animation loop inheritance.
When the style change did not happened externally (e.g. by a different tool) use set method to get best performance:
Fat.animate(element, { top: "100%" }, function(){
Fat.set(this, "top", 0).animate(this, { top: "100%" });
});
Otherwise, to solve this situation you have to add the init option:
Fat.animate(element, { top: "100%" }, function(){
this.style.top = 0; // external change somewhere happens
Fat.animate(this, { top: "100%" }, { init: true });
});
Again, this issue only occurs when using animation loops mixed with manual style changes on the same style property during the callback right before the new animation loop is called.
Strict (Options)
Considering the following example:
Fat.animate("#mydiv", { top: "100%" }, { duration: 2000 }, function(){
console.log("long");
});
// next animation will override the above one:
Fat.animate("#mydiv", { top: "100%" }, { duration: 400 }, function(){
console.log("short");
});
When you perform different animations on the same object style properties to run in parallel there is a concurrency issue. By default a dupe animation inherits the old one, so the old animation is not existing anymore. Accordingly to the example from above the console just logs "short".
To force duped animations you have to add the strict option:
// this animation cannot be overridden:
Fat.animate("#mydiv", { top: "100%" }, { duration: 2000, strict: true }, function(){
console.log("long");
});
Fat.animate("#mydiv", { top: "100%" }, { duration: 400 }, function(){
console.log("short");
});
Now the console logs "short" after 400ms and "long" after 2000ms. Although same properties cannot have two different values, so always the most early started animation gets prioritized actually.
Force (Options)
Considering the following example:
#mydiv{ top: 0px !important }
Fat.animate("#mydiv", { top: "100%" });
The css style declaration from above has the keyword !important and is preventing normal style changes.
To solve this you have to add the force option:
Fat.animate("#mydiv", { top: "100%" }, { force: true });
Render Engines
These is an experimental feature. All engines are stand-alone, you can make a custom build just with your favorite pick.
Use CSS Transitions:
Fat.transition("#mydiv", {
left: "100px",
top: "100px"
},{
delay: 1000,
duration: 2000,
ease: "easeInOut",
callback: function(){
// done
console.log(this.style.left);
}
});
Use Web Animation API:
Fat.native("#mydiv", {
left: "100px",
top: "100px"
},{
delay: 1000,
duration: 2000,
ease: "easeInOut",
callback: function(){
// done
console.log(this.style.left);
}
});
Debug
Do not use DEBUG in production builds.
If you get issues, you can temporary set the DEBUG flag to true on top of fat.js:
DEBUG = true;
This enables console logging of several processes. Just open the browsers console to make this information visible.
Profiler Stats
Do not use PROFILER in production builds.
To collect some performance statistics of your scenes you need to temporary set the PROFILER flag to true on top of fat.js:
PROFILER = true;
This enables profiling of several processes.
An array of all profiles is available on:
window.stats;
You can also just open the browsers console and enter this line to get stats.
The index of the array corresponds to the scene.id.
Get stats from a specific scene:
scene.stats;
The returning stats payload is divided into several categories. Each of these category provides its own statistic values.
Profiler Stats Properties
Custom Builds
You need Node.js including Node Package Manager (NPM)
Install Dependencies:
npm install
Full Build:
npm run build
Light Build:
npm run build-light
Compact Build:
npm run build-compact
Custom Build:
npm run build-custom SUPPORT_EASE_IN_CUBIC=true SUPPORT_CONTROL=true
On custom builds each build flag will be set to false by default.
Alternatively (Custom Build):
node compile support_control=true
The custom build will be saved to fat.custom.xxxxx.js (the "xxxxx" is a hash based on the used build flags).
Supported Build Flags
Copyright 2019 Nextapps GmbH Released under the Apache 2.0 License