motion
Version:
The Motion library for the web
147 lines (144 loc) • 6.7 kB
JavaScript
import { __rest } from 'tslib';
import { progress } from 'popmotion';
import { resolveOption } from '../../../utils/stagger.es.js';
import { defaultOffset, fillOffset } from '../../js/utils/offset.es.js';
import { animateStyle } from '../animate-style.es.js';
import { createAnimationControls } from '../utils/controls.es.js';
import { defaults } from '../utils/defaults.es.js';
import { keyframesList } from '../utils/keyframes.es.js';
import { getOptions } from '../utils/options.es.js';
import { resolveElements } from '../utils/resolve-elements.es.js';
import { calcNextTime } from './utils/calc-time.es.js';
import { addKeyframes } from './utils/edit.es.js';
import { compareByTime } from './utils/sort.es.js';
function timeline(definition, options = {}) {
const animations = [];
const animationDefinitions = createAnimationsFromTimeline(definition, options);
for (let i = 0; i < animationDefinitions.length; i++) {
const animation = animateStyle(...animationDefinitions[i]);
animation && animations.push(animation);
}
return createAnimationControls(animations);
}
function createAnimationsFromTimeline(definition, _a = {}) {
var { defaultOptions = {} } = _a, timelineOptions = __rest(_a, ["defaultOptions"]);
const animationDefinitions = [];
const elementSequences = new Map();
const elementCache = {};
const timeLabels = new Map();
let prevTime = 0;
let currentTime = 0;
let totalDuration = 0;
/**
* Build the timeline by mapping over the definition array and converting
* the definitions into keyframes and offsets with absolute time values.
* These will later get converted into relative offsets in a second pass.
*/
for (let i = 0; i < definition.length; i++) {
const [elementDefinition, keyframes, options = {}] = definition[i];
/**
* If a relative or absolute time value has been specified we need to resolve
* it in relation to the currentTime.
*/
if (options.at !== undefined) {
currentTime = calcNextTime(currentTime, options.at, prevTime, timeLabels);
}
/**
* Keep track of the maximum duration in this definition. This will be
* applied to currentTime once the definition has been parsed.
*/
let maxDuration = 0;
/**
* Find all the elements specified in the definition and parse value
* keyframes from their timeline definitions.
*/
const elements = resolveElements(elementDefinition, elementCache);
const numElements = elements.length;
for (let elementIndex = 0; elementIndex < numElements; elementIndex++) {
const element = elements[elementIndex];
const elementSequence = getElementSequence(element, elementSequences);
for (const key in keyframes) {
const valueSequence = getValueSequence(key, elementSequence);
const valueKeyframes = keyframesList(keyframes[key]);
const valueOptions = getOptions(options, key);
const { duration = defaultOptions.duration || defaults.duration, easing = defaultOptions.easing || defaults.easing, offset = defaultOffset(valueKeyframes.length), } = valueOptions;
const delay = resolveOption(options.delay, elementIndex, numElements) || 0;
const startTime = currentTime + delay;
const targetTime = startTime + duration;
if (offset.length === 1 && offset[0] === 0) {
offset[1] = 1;
}
/**
* Fill out if offset if fewer offsets than keyframes
*/
const remainder = length - valueKeyframes.length;
remainder > 0 && fillOffset(offset, remainder);
/**
* If only one value has been set, ie [1], push a null to the start of
* the keyframe array. This will let us mark a keyframe at this point
* that will later be hydrated with the previous value.
*/
valueKeyframes.length === 1 && valueKeyframes.unshift(null);
/**
* Add keyframes, mapping offsets to absolute time.
*/
addKeyframes(valueSequence, valueKeyframes, easing, offset, startTime, targetTime);
maxDuration = Math.max(delay + duration, maxDuration);
totalDuration = Math.max(targetTime, totalDuration);
}
}
prevTime = currentTime;
currentTime += maxDuration;
}
/**
* For every element and value combination create a new animation.
*/
elementSequences.forEach((valueSequences, element) => {
for (const key in valueSequences) {
const valueSequence = valueSequences[key];
/**
* Arrange all the keyframes in ascending time order.
*/
valueSequence.sort(compareByTime);
const keyframes = [];
const valueOffset = [];
const valueEasing = [];
/**
* For each keyframe, translate absolute times into
* relative offsets based on the total duration of the timeline.
*/
for (let i = 0; i < valueSequence.length; i++) {
const { at, value, easing } = valueSequence[i];
keyframes.push(value);
valueOffset.push(progress(0, totalDuration, at));
valueEasing.push(easing || defaults.easing);
}
/**
* If the generated animation doesn't end on the final keyframe,
* provide one with a null wildcard value. This will ensure it
* stays static until the end of the animation.
*/
if (valueOffset[valueOffset.length - 1] !== 1) {
valueOffset.push(1);
keyframes.push(null);
}
animationDefinitions.push([
element,
key,
keyframes,
Object.assign(Object.assign(Object.assign({}, defaultOptions), { duration: totalDuration, easing: valueEasing, offset: valueOffset }), timelineOptions),
]);
}
});
return animationDefinitions;
}
function getElementSequence(element, sequences) {
!sequences.has(element) && sequences.set(element, {});
return sequences.get(element);
}
function getValueSequence(name, sequences) {
if (!sequences[name])
sequences[name] = [];
return sequences[name];
}
export { createAnimationsFromTimeline, timeline };