motion
Version:
The Motion library for the web
152 lines (147 loc) • 6.93 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var popmotion = require('popmotion');
var stagger = require('../../../utils/stagger.cjs.js');
var offset = require('../../js/utils/offset.cjs.js');
var animateStyle = require('../animate-style.cjs.js');
var controls = require('../utils/controls.cjs.js');
var defaults = require('../utils/defaults.cjs.js');
var keyframes = require('../utils/keyframes.cjs.js');
var options = require('../utils/options.cjs.js');
var resolveElements = require('../utils/resolve-elements.cjs.js');
var calcTime = require('./utils/calc-time.cjs.js');
var edit = require('./utils/edit.cjs.js');
var sort = require('./utils/sort.cjs.js');
function timeline(definition, options = {}) {
const animations = [];
const animationDefinitions = createAnimationsFromTimeline(definition, options);
for (let i = 0; i < animationDefinitions.length; i++) {
const animation = animateStyle.animateStyle(...animationDefinitions[i]);
animation && animations.push(animation);
}
return controls.createAnimationControls(animations);
}
function createAnimationsFromTimeline(definition, _a = {}) {
var { defaultOptions = {} } = _a, timelineOptions = tslib.__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$1, options$1 = {}] = definition[i];
/**
* If a relative or absolute time value has been specified we need to resolve
* it in relation to the currentTime.
*/
if (options$1.at !== undefined) {
currentTime = calcTime.calcNextTime(currentTime, options$1.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.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$1) {
const valueSequence = getValueSequence(key, elementSequence);
const valueKeyframes = keyframes.keyframesList(keyframes$1[key]);
const valueOptions = options.getOptions(options$1, key);
const { duration = defaultOptions.duration || defaults.defaults.duration, easing = defaultOptions.easing || defaults.defaults.easing, offset: offset$1 = offset.defaultOffset(valueKeyframes.length), } = valueOptions;
const delay = stagger.resolveOption(options$1.delay, elementIndex, numElements) || 0;
const startTime = currentTime + delay;
const targetTime = startTime + duration;
if (offset$1.length === 1 && offset$1[0] === 0) {
offset$1[1] = 1;
}
/**
* Fill out if offset if fewer offsets than keyframes
*/
const remainder = length - valueKeyframes.length;
remainder > 0 && offset.fillOffset(offset$1, 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.
*/
edit.addKeyframes(valueSequence, valueKeyframes, easing, offset$1, 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(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(popmotion.progress(0, totalDuration, at));
valueEasing.push(easing || defaults.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];
}
exports.createAnimationsFromTimeline = createAnimationsFromTimeline;
exports.timeline = timeline;