UNPKG

framer-motion

Version:

A simple and powerful JavaScript animation library

113 lines (110 loc) 4.48 kB
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 };