motion
Version:
The Motion library for the web
149 lines (146 loc) • 6.28 kB
JavaScript
import { getAnimationData } from './data.es.js';
import { isCssVar, registerCssVariable } from './utils/css-var.es.js';
import { noop } from '../../utils/noop.es.js';
import { ms } from './utils/time.es.js';
import { isTransform, transformAlias, addTransformToElement, asTransformCssVar, transformPropertyDefinitions } from './utils/transforms.es.js';
import { stopAnimation } from './utils/stop-animation.es.js';
import { isEasingList, convertEasing } from './utils/easing.es.js';
import { supports } from './utils/feature-detection.es.js';
import { createCssVariableRenderer, createStyleRenderer } from './utils/apply.es.js';
import { animateNumber } from '../js/animate-number.es.js';
import { hydrateKeyframes, keyframesList } from './utils/keyframes.es.js';
import { style } from './style.es.js';
import { defaults } from './utils/defaults.es.js';
function animateStyle(element, name, keyframesDefinition, options = {}) {
let { duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, repeat = defaults.repeat, easing = defaults.easing, direction, offset, allowWebkitAcceleration = false, } = options;
const data = getAnimationData(element);
let canAnimateNatively = supports.waapi();
let render = noop;
const valueIsTransform = isTransform(name);
/**
* If this is an individual transform, we need to map its
* key to a CSS variable and update the element's transform style
*/
if (valueIsTransform) {
if (transformAlias[name])
name = transformAlias[name];
addTransformToElement(element, name);
name = asTransformCssVar(name);
}
/**
* Get definition of value, this will be used to convert numerical
* keyframes into the default value type.
*/
const definition = transformPropertyDefinitions.get(name);
/**
* Replace null values with the previous keyframe value, or read
* it from the DOM if it's the first keyframe.
*
* TODO: This needs to come after the valueIsTransform
* check so it can correctly read the underlying value.
* Should make a test for this.
*/
let keyframes = hydrateKeyframes(keyframesList(keyframesDefinition), element, name);
stopCurrentAnimation(data, name);
/**
* If this is a CSS variable we need to register it with the browser
* before it can be animated natively. We also set it with setProperty
* rather than directly onto the element.style object.
*/
if (isCssVar(name)) {
render = createCssVariableRenderer(element, name);
if (supports.cssRegisterProperty()) {
registerCssVariable(name);
}
else {
canAnimateNatively = false;
}
}
else {
render = createStyleRenderer(element, name);
}
let animation;
/**
* If we can animate this value with WAAPI, do so. Currently this only
* feature detects CSS.registerProperty but could check WAAPI too.
*/
if (canAnimateNatively) {
/**
* Convert numbers to default value types. Currently this only supports
* transforms but it could also support other value types.
*/
if (definition) {
keyframes = keyframes.map((value) => typeof value === "number" ? definition.toDefaultUnit(value) : value);
}
if (!supports.partialKeyframes() && keyframes.length === 1) {
keyframes.unshift(style.get(element, name));
}
const animationOptions = {
delay: ms(delay),
duration: ms(duration),
endDelay: ms(endDelay),
easing: !isEasingList(easing) ? convertEasing(easing) : undefined,
direction,
iterations: repeat + 1,
};
animation = element.animate({
[name]: keyframes,
offset,
easing: isEasingList(easing) ? easing.map(convertEasing) : undefined,
}, animationOptions);
/**
* Polyfill finished Promise in browsers that don't support it
*/
if (!animation.finished) {
animation.finished = new Promise((resolve, reject) => {
animation.onfinish = resolve;
animation.oncancel = reject;
});
}
const target = keyframes[keyframes.length - 1];
animation.finished.then(() => render(target)).catch(noop);
/**
* This forces Webkit to run animations on the main thread by exploiting
* this condition:
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp?rev=281238#L1099
*
* This fixes Webkit's timing bugs, like accelerated animations falling
* out of sync with main thread animations and massive delays in starting
* accelerated animations in WKWebView.
*/
if (!allowWebkitAcceleration)
animation.playbackRate = 1.000001;
}
else if (valueIsTransform && keyframes.every(isNumber)) {
if (keyframes.length === 1) {
keyframes.unshift(style.get(element, name) || (definition === null || definition === void 0 ? void 0 : definition.initialValue) || 0);
}
/**
* Transform styles are currently only accepted as numbers of
* their default value type, so here we loop through and map
* them to numbers.
*/
keyframes = keyframes.map((value) => typeof value === "string" ? parseFloat(value) : value);
if (definition) {
const applyStyle = render;
render = (v) => applyStyle(definition.toDefaultUnit(v));
}
animation = animateNumber(render, keyframes, options);
}
else {
const target = keyframes[keyframes.length - 1];
render(definition && typeof target === "number"
? definition.toDefaultUnit(target)
: target);
}
data.activeAnimations[name] = animation;
return animation;
}
function stopCurrentAnimation(data, name) {
if (data.activeAnimations[name]) {
stopAnimation(data.activeAnimations[name]);
data.activeAnimations[name] = undefined;
}
}
const isNumber = (value) => typeof value === "number";
export { animateStyle };