framer-motion
Version:
A simple and powerful JavaScript animation library
113 lines (110 loc) • 4.48 kB
JavaScript
import { NativeAnimationControls, isGenerator, createGeneratorEasing, supportsLinearEasing } from 'motion-dom';
import { invariant, secondsToMilliseconds } from 'motion-utils';
import { startWaapiAnimation } from './index.mjs';
import { browserNumberValueTypes } from '../../../render/dom/value-types/number-browser.mjs';
import { getFinalKeyframe } from './utils/get-final-keyframe.mjs';
import { setCSSVar, setStyle } from './utils/style.mjs';
import { supportsPartialKeyframes } from './utils/supports-partial-keyframes.mjs';
import { supportsWaapi } from './utils/supports-waapi.mjs';
const state = new WeakMap();
function hydrateKeyframes(valueName, keyframes, read) {
for (let i = 0; i < keyframes.length; i++) {
if (keyframes[i] === null) {
keyframes[i] = i === 0 ? read() : keyframes[i - 1];
}
if (typeof keyframes[i] === "number" &&
browserNumberValueTypes[valueName]) {
keyframes[i] = browserNumberValueTypes[valueName].transform(keyframes[i]);
}
}
if (!supportsPartialKeyframes() && keyframes.length < 2) {
keyframes.unshift(read());
}
}
const defaultEasing = "easeOut";
function getElementAnimationState(element) {
const animationState = state.get(element) || new Map();
state.set(element, animationState);
return state.get(element);
}
class NativeAnimation extends NativeAnimationControls {
constructor(element, valueName, valueKeyframes, options) {
const isCSSVar = valueName.startsWith("--");
invariant(typeof options.type !== "string", `animateMini doesn't support "type" as a string. Did you mean to import { spring } from "framer-motion"?`);
const existingAnimation = getElementAnimationState(element).get(valueName);
existingAnimation && existingAnimation.stop();
const readInitialKeyframe = () => {
return valueName.startsWith("--")
? element.style.getPropertyValue(valueName)
: window.getComputedStyle(element)[valueName];
};
if (!Array.isArray(valueKeyframes)) {
valueKeyframes = [valueKeyframes];
}
hydrateKeyframes(valueName, valueKeyframes, readInitialKeyframe);
// TODO: Replace this with toString()?
if (isGenerator(options.type)) {
const generatorOptions = createGeneratorEasing(options, 100, options.type);
options.ease = supportsLinearEasing()
? generatorOptions.ease
: defaultEasing;
options.duration = secondsToMilliseconds(generatorOptions.duration);
options.type = "keyframes";
}
else {
options.ease = options.ease || defaultEasing;
}
const onFinish = () => {
this.setValue(element, valueName, getFinalKeyframe(valueKeyframes, options));
this.cancel();
this.resolveFinishedPromise();
};
const init = () => {
this.setValue = isCSSVar ? setCSSVar : setStyle;
this.options = options;
this.updateFinishedPromise();
this.removeAnimation = () => {
const elementState = state.get(element);
elementState && elementState.delete(valueName);
};
};
if (!supportsWaapi()) {
super();
init();
onFinish();
}
else {
super(startWaapiAnimation(element, valueName, valueKeyframes, options));
init();
if (options.autoplay === false) {
this.animation.pause();
}
this.animation.onfinish = onFinish;
getElementAnimationState(element).set(valueName, this);
}
}
/**
* Allows the returned animation to be awaited or promise-chained. Currently
* resolves when the animation finishes at all but in a future update could/should
* reject if its cancels.
*/
then(resolve, reject) {
return this.currentFinishedPromise.then(resolve, reject);
}
updateFinishedPromise() {
this.currentFinishedPromise = new Promise((resolve) => {
this.resolveFinishedPromise = resolve;
});
}
play() {
if (this.state === "finished") {
this.updateFinishedPromise();
}
super.play();
}
cancel() {
this.removeAnimation();
super.cancel();
}
}
export { NativeAnimation };