UNPKG

motion

Version:

The Motion library for the web

147 lines (144 loc) 6.7 kB
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 };