Comparing version 1.2.1 to 1.3.0
(() => { | ||
// src/generator.ts | ||
// src/transCssManager.ts | ||
var EASE = { | ||
@@ -20,7 +20,12 @@ cubicBezier: (...points) => `cubic-bezier(${points.map((p) => p.toString()).reduce((curr, next) => curr + "," + next)})`, | ||
}; | ||
var generateTransition = (prevState, transition) => distinct([ | ||
...Object.keys(transition.state ?? {}), | ||
...Object.keys(prevState.curr) | ||
]).map((p) => `${p} ${transition.ms ?? prevState.lastMs}ms ${transition.easing ?? prevState.lastEase}`).join(","); | ||
var distinct = (arr) => Array.from(new Set(arr)); | ||
function updateTransition(state, trans) { | ||
for (const prop in trans.state) | ||
if (!state.running.has(prop)) | ||
state.running.set(prop, [ | ||
trans.ms ?? state.lastMs, | ||
prop, | ||
trans.easing ?? state.lastEase | ||
]); | ||
} | ||
var generateTransition = (state) => Array.from(state.running.values()).map(([ms, prop, ease]) => `${prop} ${ms ?? state.lastMs}ms ${ease ?? state.lastEase}`).join(","); | ||
@@ -49,5 +54,6 @@ // src/shared.ts | ||
queue: [], | ||
transitionPromises: [], | ||
transitionPromises: /* @__PURE__ */ new Map(), | ||
lastEase: EASE.ease, | ||
lastMs: 100 | ||
lastMs: 100, | ||
running: /* @__PURE__ */ new Map() | ||
}; | ||
@@ -65,6 +71,9 @@ sanitiseStyleObject(newState.orig); | ||
const state = getOrInitStore(elem); | ||
state.queue.push(transition); | ||
if (transition.detached) | ||
startAnimating(elem, transition); | ||
else | ||
state.queue.push(transition); | ||
let resolve; | ||
const promise = new Promise((res) => resolve = res); | ||
state.transitionPromises.push([transition, promise, () => resolve()]); | ||
state.transitionPromises.set(transition, [promise, () => resolve()]); | ||
return [state.queue.length === 1, promise]; | ||
@@ -119,3 +128,4 @@ } | ||
Object.assign(state.curr, transition.state); | ||
state.transitionPromises.shift()?.[2](); | ||
state.transitionPromises.get(transition)?.[1](); | ||
state.transitionPromises.delete(transition); | ||
return true; | ||
@@ -129,8 +139,9 @@ } | ||
var abortAnimation = (elem) => elem.style.transition = "none"; | ||
function startAnimating(elem) { | ||
function startAnimating(elem, transition) { | ||
const state = getOrInitStore(elem); | ||
const transition = state.queue[0]; | ||
if (!transition?.detached) | ||
transition = state.queue[0]; | ||
if (!transition) | ||
return; | ||
const transitionString = generateTransition(state, transition); | ||
updateTransition(state, transition); | ||
if (transition.reset) | ||
@@ -140,9 +151,12 @@ state.curr = { ...transition.state }; | ||
Object.assign(state.curr, transition.state); | ||
elem.style.transition = generateTransition(state); | ||
updateStyles(elem); | ||
state.lastMs = transition.ms ?? state.lastMs; | ||
state.lastEase = transition.easing ?? state.lastEase; | ||
elem.style.transition = transitionString; | ||
updateStyles(elem); | ||
enqueue(() => { | ||
state.transitionPromises.get(transition)?.[1](); | ||
state.transitionPromises.delete(transition); | ||
if (transition.detached) | ||
return; | ||
state.queue.shift(); | ||
state.transitionPromises.shift()?.[2](); | ||
startAnimating(elem); | ||
@@ -171,2 +185,5 @@ }, state.lastMs + 1); | ||
}; | ||
SVGElement.prototype.forcePop = HTMLElement.prototype.forcePop = function() { | ||
popAll(this); | ||
}; | ||
SVGElement.prototype.removeCrossAni = HTMLElement.prototype.removeCrossAni = function() { | ||
@@ -180,7 +197,2 @@ const store = getOrInitStore(this); | ||
}; | ||
var orig = Element.prototype.remove; | ||
Element.prototype.remove = function() { | ||
stateStore.delete(this); | ||
orig.call(this); | ||
}; | ||
var unload = () => { | ||
@@ -187,0 +199,0 @@ for (const elem of stateStore.keys()) |
@@ -25,3 +25,3 @@ var __defProp = Object.defineProperty; | ||
module.exports = __toCommonJS(animator_exports); | ||
var import_generator = require("./generator"); | ||
var import_transCssManager = require("./transCssManager"); | ||
var import_util = require("./util"); | ||
@@ -38,3 +38,4 @@ var import_queue = require("./queue"); | ||
Object.assign(state.curr, transition.state); | ||
state.transitionPromises.shift()?.[2](); | ||
state.transitionPromises.get(transition)?.[1](); | ||
state.transitionPromises.delete(transition); | ||
return true; | ||
@@ -48,8 +49,9 @@ } | ||
const abortAnimation = (elem) => elem.style.transition = "none"; | ||
function startAnimating(elem) { | ||
function startAnimating(elem, transition) { | ||
const state = (0, import_util.getOrInitStore)(elem); | ||
const transition = state.queue[0]; | ||
if (!transition?.detached) | ||
transition = state.queue[0]; | ||
if (!transition) | ||
return; | ||
const transitionString = (0, import_generator.generateTransition)(state, transition); | ||
(0, import_transCssManager.updateTransition)(state, transition); | ||
if (transition.reset) | ||
@@ -59,11 +61,14 @@ state.curr = { ...transition.state }; | ||
Object.assign(state.curr, transition.state); | ||
elem.style.transition = (0, import_transCssManager.generateTransition)(state); | ||
(0, import_util.updateStyles)(elem); | ||
state.lastMs = transition.ms ?? state.lastMs; | ||
state.lastEase = transition.easing ?? state.lastEase; | ||
elem.style.transition = transitionString; | ||
(0, import_util.updateStyles)(elem); | ||
(0, import_queue.enqueue)(() => { | ||
state.transitionPromises.get(transition)?.[1](); | ||
state.transitionPromises.delete(transition); | ||
if (transition.detached) | ||
return; | ||
state.queue.shift(); | ||
state.transitionPromises.shift()?.[2](); | ||
startAnimating(elem); | ||
}, state.lastMs + 1); | ||
} |
var import__ = require("."); | ||
var import_generator = require("./generator"); | ||
window.EASE = import_generator.EASE; | ||
window.JUMP = import_generator.JUMP; | ||
var import_transCssManager = require("./transCssManager"); | ||
window.EASE = import_transCssManager.EASE; | ||
window.JUMP = import_transCssManager.JUMP; | ||
window.unloadCrossani = import__.unload; |
@@ -20,4 +20,4 @@ var __defProp = Object.defineProperty; | ||
__export(src_exports, { | ||
EASE: () => import_generator.EASE, | ||
JUMP: () => import_generator.JUMP, | ||
EASE: () => import_transCssManager.EASE, | ||
JUMP: () => import_transCssManager.JUMP, | ||
unload: () => unload | ||
@@ -29,3 +29,3 @@ }); | ||
var import_util = require("./util"); | ||
var import_generator = require("./generator"); | ||
var import_transCssManager = require("./transCssManager"); | ||
SVGElement.prototype.doTransition = HTMLElement.prototype.doTransition = function(transOrName) { | ||
@@ -49,2 +49,5 @@ (0, import_util.sanitiseTransitions)(this); | ||
}; | ||
SVGElement.prototype.forcePop = HTMLElement.prototype.forcePop = function() { | ||
(0, import_animator.popAll)(this); | ||
}; | ||
SVGElement.prototype.removeCrossAni = HTMLElement.prototype.removeCrossAni = function() { | ||
@@ -58,7 +61,2 @@ const store = (0, import_util.getOrInitStore)(this); | ||
}; | ||
const orig = Element.prototype.remove; | ||
Element.prototype.remove = function() { | ||
import_shared.stateStore.delete(this); | ||
orig.call(this); | ||
}; | ||
const unload = () => { | ||
@@ -65,0 +63,0 @@ for (const elem of import_shared.stateStore.keys()) |
@@ -28,4 +28,5 @@ var __defProp = Object.defineProperty; | ||
module.exports = __toCommonJS(util_exports); | ||
var import_generator = require("./generator"); | ||
var import_transCssManager = require("./transCssManager"); | ||
var import_shared = require("./shared"); | ||
var import_animator = require("./animator"); | ||
function cloneStyles(styles) { | ||
@@ -49,5 +50,6 @@ const cloned = {}; | ||
queue: [], | ||
transitionPromises: [], | ||
lastEase: import_generator.EASE.ease, | ||
lastMs: 100 | ||
transitionPromises: /* @__PURE__ */ new Map(), | ||
lastEase: import_transCssManager.EASE.ease, | ||
lastMs: 100, | ||
running: /* @__PURE__ */ new Map() | ||
}; | ||
@@ -65,6 +67,9 @@ sanitiseStyleObject(newState.orig); | ||
const state = getOrInitStore(elem); | ||
state.queue.push(transition); | ||
if (transition.detached) | ||
(0, import_animator.startAnimating)(elem, transition); | ||
else | ||
state.queue.push(transition); | ||
let resolve; | ||
const promise = new Promise((res) => resolve = res); | ||
state.transitionPromises.push([transition, promise, () => resolve()]); | ||
state.transitionPromises.set(transition, [promise, () => resolve()]); | ||
return [state.queue.length === 1, promise]; | ||
@@ -71,0 +76,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { generateTransition } from "./generator"; | ||
import { generateTransition, updateTransition } from "./transCssManager"; | ||
import { getOrInitStore, updateStyles } from "./util"; | ||
@@ -16,3 +16,4 @@ import { enqueue } from "./queue"; | ||
// resolve | ||
state.transitionPromises.shift()?.[2](); | ||
state.transitionPromises.get(transition)?.[1](); | ||
state.transitionPromises.delete(transition); | ||
return true; | ||
@@ -26,10 +27,9 @@ } | ||
export const abortAnimation = (elem) => (elem.style.transition = "none"); | ||
export function startAnimating(elem) { | ||
export function startAnimating(elem, transition) { | ||
const state = getOrInitStore(elem); | ||
const transition = state.queue[0]; | ||
if (!transition?.detached) | ||
transition = state.queue[0]; | ||
if (!transition) | ||
return; | ||
// generates the style transition: property string | ||
// needs to be run before updating state.curr or some values may not work correctly | ||
const transitionString = generateTransition(state, transition); | ||
updateTransition(state, transition); | ||
// update styles | ||
@@ -40,12 +40,15 @@ if (transition.reset) | ||
Object.assign(state.curr, transition.state); | ||
// run transition | ||
elem.style.transition = generateTransition(state); | ||
updateStyles(elem); | ||
state.lastMs = transition.ms ?? state.lastMs; | ||
state.lastEase = transition.easing ?? state.lastEase; | ||
// run transition | ||
elem.style.transition = transitionString; | ||
updateStyles(elem); | ||
enqueue(() => { | ||
state.transitionPromises.get(transition)?.[1](); | ||
state.transitionPromises.delete(transition); | ||
if (transition.detached) | ||
return; | ||
state.queue.shift(); | ||
state.transitionPromises.shift()?.[2](); | ||
startAnimating(elem); | ||
}, state.lastMs + 1); // lmao | ||
} |
import { unload } from "."; | ||
import { EASE, JUMP } from "./generator"; | ||
import { EASE, JUMP } from "./transCssManager"; | ||
// @ts-expect-error | ||
@@ -4,0 +4,0 @@ window.EASE = EASE; |
import { abortAnimation, popAll, startAnimating } from "./animator"; | ||
import { stateStore } from "./shared"; | ||
import { getOrInitStore, queueTransition, sanitiseTransitions, updateStyles, whenTransitionAborts, } from "./util"; | ||
export { EASE, JUMP } from "./generator"; | ||
export { EASE, JUMP } from "./transCssManager"; | ||
SVGElement.prototype.doTransition = HTMLElement.prototype.doTransition = | ||
@@ -29,2 +29,6 @@ function (transOrName) { | ||
}; | ||
SVGElement.prototype.forcePop = HTMLElement.prototype.forcePop = | ||
function () { | ||
popAll(this); | ||
}; | ||
SVGElement.prototype.removeCrossAni = HTMLElement.prototype.removeCrossAni = | ||
@@ -39,7 +43,2 @@ function () { | ||
}; | ||
const orig = Element.prototype.remove; | ||
Element.prototype.remove = function () { | ||
stateStore.delete(this); | ||
orig.call(this); | ||
}; | ||
export const unload = () => { | ||
@@ -46,0 +45,0 @@ for (const elem of stateStore.keys()) |
@@ -1,3 +0,4 @@ | ||
import { EASE } from "./generator"; | ||
import { EASE } from "./transCssManager"; | ||
import { stateStore } from "./shared"; | ||
import { startAnimating } from "./animator"; | ||
/** Converts a CSSStyleDeclaration to a Record<string, string> */ | ||
@@ -26,5 +27,6 @@ export function cloneStyles(styles) { | ||
queue: [], | ||
transitionPromises: [], | ||
transitionPromises: new Map(), | ||
lastEase: EASE.ease, | ||
lastMs: 100, | ||
running: new Map() | ||
}; | ||
@@ -45,6 +47,9 @@ sanitiseStyleObject(newState.orig); | ||
const state = getOrInitStore(elem); | ||
state.queue.push(transition); | ||
if (transition.detached) | ||
startAnimating(elem, transition); | ||
else | ||
state.queue.push(transition); | ||
let resolve; | ||
const promise = new Promise((res) => (resolve = res)); | ||
state.transitionPromises.push([transition, promise, () => resolve()]); | ||
state.transitionPromises.set(transition, [promise, () => resolve()]); | ||
return [state.queue.length === 1, promise]; | ||
@@ -51,0 +56,0 @@ } |
@@ -0,4 +1,5 @@ | ||
import { Transition } from "./types"; | ||
/** Pops all transitions off the queue instantly and applies relevant CSS */ | ||
export declare function popAll(elem: HTMLElement | SVGElement): void; | ||
export declare const abortAnimation: (elem: HTMLElement | SVGElement) => string; | ||
export declare function startAnimating(elem: HTMLElement | SVGElement): void; | ||
export declare function startAnimating(elem: HTMLElement | SVGElement, transition?: Transition): void; |
@@ -1,3 +0,3 @@ | ||
export { EASE, JUMP } from "./generator"; | ||
export { EASE, JUMP } from "./transCssManager"; | ||
export declare const unload: () => void; | ||
export type { Transition } from "./types"; |
@@ -13,3 +13,6 @@ /** Represents a transition that may run on an element at any given time */ | ||
cutOff?: boolean; | ||
/** Don't track and queue this */ | ||
detached?: boolean; | ||
} | ||
export declare type CssTransition = [number | undefined, string, string | undefined]; | ||
/** @internal */ | ||
@@ -24,3 +27,3 @@ export interface ElementState { | ||
/** Stores promises for transition completion */ | ||
transitionPromises: [Transition, Promise<void>, () => void][]; | ||
transitionPromises: Map<Transition, [Promise<void>, () => void]>; | ||
/** Stores the previous ms value of the element */ | ||
@@ -30,2 +33,4 @@ lastMs: number; | ||
lastEase: string; | ||
/** Current running transitions */ | ||
running: Map<string, CssTransition>; | ||
} | ||
@@ -37,6 +42,8 @@ declare global { | ||
/** Runs transitions defined in Element.transitions by name */ | ||
doTransition(name: Transition | string): void; | ||
doTransition(name: Transition | string): Promise<void>; | ||
/** Removes CrossAni from this element */ | ||
removeCrossAni(): void; | ||
/** Stops currently running animations */ | ||
forcePop(): void; | ||
} | ||
} |
{ | ||
"name": "crossani", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "A silky smooth declarative animation library for the web.", | ||
@@ -15,4 +15,4 @@ "main": "dist/cjs/index.js", | ||
"scripts": { | ||
"build": "tsc && esbuild src/*.ts --format=cjs --outdir=dist/cjs && esbuild src/browser.ts --bundle --outfile=dist/bundle.js" | ||
"prepublish": "tsc && esbuild src/*.ts --format=cjs --outdir=dist/cjs && esbuild src/browser.ts --bundle --outfile=dist/bundle.js" | ||
} | ||
} |
134
README.md
@@ -13,3 +13,3 @@ # CrossAni | ||
However: this can have massive performance repurcussions. | ||
However: this can have performance repurcussions. | ||
@@ -20,3 +20,3 @@ CrossAni uses CSS transitions to fix this problem, using the browser's built in animating tools. | ||
The main other way to achieve this is the Web Animations API (WAAPI). | ||
The main other way to achieve this is the Web Animations API (WAAPI) like Motion One does. | ||
@@ -60,12 +60,28 @@ ### Size Matters | ||
Any element that should be animated needs a `transitions` property with a key-value record of strings to state objects. | ||
Any element that should be animated can have a `transitions` property with a key-value record of strings to transition objects. | ||
A state object contains the following things: | ||
You may also pass transition objects directly to doTransition, | ||
which is helpful for programmatically generated animations for example. | ||
A transition object contains the following things: | ||
- `ms`: the time it should take to finish transitioning to that state | ||
- `easing`: An easing function to use, all exported on the `EASE` object. | ||
- `state`: An object containing the CSS values to set on the element this state. | ||
- `cutOff`: (OPTIONAL) see behaviour above | ||
- `reset`: (OPTIONAL) see behaviour above | ||
- `cutOff`: see behaviour above | ||
- `reset`: see behaviour above | ||
- `detached`: if true, this transition is completely independent of the global queue and runs instantly. | ||
Transition objects allow all properties to be undefined, and will provide useful defaults: | ||
```js | ||
({ | ||
state: {}, | ||
ms: 100, // falls through from past transition, this value is for the first transition | ||
easing: EASE.ease, // see above comment | ||
cutOff: false, | ||
reset: false, | ||
detached: false | ||
}) | ||
``` | ||
An example setup would be as follows: | ||
@@ -78,3 +94,2 @@ | ||
default: { | ||
state: {}, | ||
ms: 500, | ||
@@ -88,4 +103,3 @@ easing: EASE.inOut, | ||
state: { transform: "scale(1.1)" }, | ||
ms: 250, | ||
easing: EASE.inOut, | ||
ms: 250 | ||
}, | ||
@@ -117,3 +131,3 @@ }; | ||
box.doTransition("moveright"); | ||
box.doTransition("slowgrop"); | ||
box.doTransition("slowgrow"); | ||
await box.doTransition("fadetextout"); | ||
@@ -132,4 +146,2 @@ box.innerText = "the text is now different"; | ||
Either way, you're doing much better with CrossAni than JS animations, even if you're not getting the most blazing speed! | ||
Non `transform` _movements_ are slower as, although the browser is running the animation, | ||
@@ -150,3 +162,3 @@ repaints and often reflows are necessary, which are expensive. | ||
But also, SVG `d` attributes still cannot be animated with CSS transitions. | ||
However, CrossAni does not support some SVG attributes such as `d`. | ||
@@ -158,3 +170,3 @@ ### Bundle size | ||
| Pkg | Raw | Gzip | Brotli | | ||
| -------- | ------- | ------- | ------- | | ||
|----------|---------|---------|---------| | ||
| crossani | 2.5kb | 1.12kb | 1.05kb | | ||
@@ -167,48 +179,48 @@ | motion | 15.07kb | 6.34kb | 6.17kb | | ||
| | Feature | CrossAni | Motion | AnimeJS | Greensock | | ||
| ------------ | -----------------: | :---------: | :-------: | :----------: | :-------: | | ||
| **Values** | CSS `transform` | ✅ | ✅ | ❌ | ✅ | | ||
| | Named colours | ✅ | ✅ | ❌ | Partial | | ||
| | Colour type conv | ✅ | ✅ | Wrong interp | ✅ | | ||
| | To/from CSS vars | ✅ | ✅ | ❌ | ❌ | | ||
| | To/from CSS funcs | ✅ | ✅ | ❌ | ❌ | | ||
| | Animate CSS vars | ❌ | ✅ | ❌ | ❌ | | ||
| | Simple keyframes | ❌ Soon? | ✅ | ✅ | ❌ | | ||
| | Wildcard keyframes | N/A | ✅ | ❌ | ❌ | | ||
| | Relative values | ❌ | ✅ | ❌ | ❌ | | ||
| **Output** | Element styles | ✅ | ✅ | ✅ | ✅ | | ||
| | Element attrs | ❌ | ❌ | ✅ | ✅ | | ||
| | Custom animations | ❌ | ✅ | ✅ | ✅ | | ||
| **Options** | Duration | ✅ | ✅ | ✅ | ✅ | | ||
| | Direction | ✅ | ✅ | ✅ | ✅ | | ||
| | Repeat | ❌ | ✅ | ✅ | ✅ | | ||
| | Delay | ❌ | ✅ | ✅ | ✅ | | ||
| | End delay | ❌ | ✅ | ✅ | ✅ | | ||
| | Repeat delay | ❌ | ✅ | ❌ | ✅ | | ||
| **Stagger** | Stagger | ❌ | ✅ +.1kb | ✅ | ✅ | | ||
| **Timeline** | Timeline | ❌ | ✅ +.6kb | ✅ | ✅ | | ||
| **Controls** | Play | ✅ | ✅ | ✅ | ✅ | | ||
| | Pause | ❌ | ✅ | ✅ | ✅ | | ||
| | Finish | ❌ | ✅ | ✅ | ✅ | | ||
| | Reverse | ❌ | ✅ | ✅ | ✅ | | ||
| | Stop | ❌ | ✅ | ✅ | ✅ | | ||
| | Playback rate | ❌ | ✅ | ✅ | ✅ | | ||
| **Easing** | Linear | ✅ | ✅ | ✅ | ✅ | | ||
| | Cubic bezier | ✅ | ✅ | ✅ | ✅ | | ||
| | Steps | ✅ | ✅ | ✅ | ✅ | | ||
| | Spring simulation | ❌ | ✅ +1kb | ❌ | ❌ | | ||
| | Glide | ❌ | ✅ +1.3kb | ❌ | ✅ $99/yr | | ||
| | Spring easing | ❌ | ❌ | ✅ | ❌ | | ||
| | Custom easings | ❌ | ❌ | ✅ | ✅ | | ||
| **Events** | Complete | ✅ | ✅ | ✅ | ✅ | | ||
| | Cancel | Fires above | ✅ | ✅ | ✅ | | ||
| | Start | ❌ | ❌ | ✅ | ✅ | | ||
| | Update | ❌ | ❌ | ✅ | ✅ | | ||
| | Repeat | N/A | ❌ | ✅ | ✅ | | ||
| **Path** | Motion path | ❌ | ✅ | ✅ | ✅ +9.5kb | | ||
| | Path morphing | ❌ | ✅ lib | ✅ = #points | ✅ $99/yr | | ||
| | Path drawing | ✅ | ✅ | ✅ | ✅ $99/yr | | ||
| **Other** | license | MIT | MIT | MIT | Custom | | ||
| | GPU acceleration | ✅ | ✅ | ❌ | ❌ | | ||
| | IE11 (ew) | ❌ | ❌ | ✅ | ✅ | | ||
| | Frameworks | ✅ | ✅ | ❌ | ❌ | | ||
| | Feature | CrossAni | Motion | AnimeJS | Greensock | | ||
|--------------|-------------------:|:--------:|:--------:|:------------:|:---------:| | ||
| **Values** | CSS `transform` | ✅ | ✅ | ❌ | ✅ | | ||
| | Named colours | ✅ | ✅ | ❌ | Partial | | ||
| | Colour type conv | ✅ | ✅ | Wrong interp | ✅ | | ||
| | To/from CSS vars | ✅ | ✅ | ❌ | ❌ | | ||
| | To/from CSS funcs | ✅ | ✅ | ❌ | ❌ | | ||
| | Animate CSS vars | ❌ | ✅ | ❌ | ❌ | | ||
| | Simple keyframes | ❌ Soon? | ✅ | ✅ | ❌ | | ||
| | Wildcard keyframes | N/A | ✅ | ❌ | ❌ | | ||
| | Relative values | ❌ | ✅ | ❌ | ❌ | | ||
| **Output** | Element styles | ✅ | ✅ | ✅ | ✅ | | ||
| | Element attrs | ❌ | ❌ | ✅ | ✅ | | ||
| | Custom animations | ❌ | ✅ | ✅ | ✅ | | ||
| **Options** | Duration | ✅ | ✅ | ✅ | ✅ | | ||
| | Direction | ✅ | ✅ | ✅ | ✅ | | ||
| | Repeat | ❌ | ✅ | ✅ | ✅ | | ||
| | Delay | ❌ | ✅ | ✅ | ✅ | | ||
| | End delay | ❌ | ✅ | ✅ | ✅ | | ||
| | Repeat delay | ❌ | ✅ | ❌ | ✅ | | ||
| **Stagger** | Stagger | ❌ | ✅ +.1kb | ✅ | ✅ | | ||
| **Timeline** | Timeline | ❌ | ✅ +.6kb | ✅ | ✅ | | ||
| **Controls** | Play | ✅ | ✅ | ✅ | ✅ | | ||
| | Pause | ❌ | ✅ | ✅ | ✅ | | ||
| | Finish | ❌ | ✅ | ✅ | ✅ | | ||
| | Reverse | ❌ | ✅ | ✅ | ✅ | | ||
| | Stop | ❌ | ✅ | ✅ | ✅ | | ||
| | Playback rate | ❌ | ✅ | ✅ | ✅ | | ||
| **Easing** | Linear | ✅ | ✅ | ✅ | ✅ | | ||
| | Cubic bezier | ✅ | ✅ | ✅ | ✅ | | ||
| | Steps | ✅ | ✅ | ✅ | ✅ | | ||
| | Spring simulation | ❌ | ✅ +1kb | ❌ | ❌ | | ||
| | Glide | ❌ | ✅ +1.3kb | ❌ | ✅ $99/yr | | ||
| | Spring easing | ❌ | ❌ | ✅ | ❌ | | ||
| | Custom easings | ❌ | ❌ | ✅ | ✅ | | ||
| **Events** | Complete | ✅ | ✅ | ✅ | ✅ | | ||
| | Cancel | ^ | ✅ | ✅ | ✅ | | ||
| | Start | ❌ | ❌ | ✅ | ✅ | | ||
| | Update | ❌ | ❌ | ✅ | ✅ | | ||
| | Repeat | N/A | ❌ | ✅ | ✅ | | ||
| **Path** | Motion path | ❌ | ✅ | ✅ | ✅ +9.5kb | | ||
| | Path morphing | ❌ | ✅ lib | ✅ = #points | ✅ $99/yr | | ||
| | Path drawing | ✅ | ✅ | ✅ | ✅ $99/yr | | ||
| **Other** | license | MIT | MIT | MIT | Custom | | ||
| | GPU acceleration | ✅ | ✅ | ❌ | ❌ | | ||
| | IE11 (ew) | ❌ | ❌ | ✅ | ✅ | | ||
| | Frameworks | ✅ | ✅ | ❌ | ❌ | |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47284
31
984
217