| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- 'use strict';
- Object.defineProperty(exports, '__esModule', { value: true });
- var motionDom = require('motion-dom');
- var motionUtils = require('motion-utils');
- const wrap = (min, max, v) => {
- const rangeSize = max - min;
- return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
- };
- const isEasingArray = (ease) => {
- return Array.isArray(ease) && typeof ease[0] !== "number";
- };
- function getEasingForSegment(easing, i) {
- return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
- }
- /*
- Value in range from progress
- Given a lower limit and an upper limit, we return the value within
- that range as expressed by progress (usually a number from 0 to 1)
- So progress = 0.5 would change
- from -------- to
- to
- from ---- to
- E.g. from = 10, to = 20, progress = 0.5 => 15
- @param [number]: Lower limit of range
- @param [number]: Upper limit of range
- @param [number]: The progress between lower and upper limits expressed 0-1
- @return [number]: Value as calculated from progress within range (not limited within range)
- */
- const mixNumber = (from, to, progress) => {
- return from + (to - from) * progress;
- };
- function fillOffset(offset, remaining) {
- const min = offset[offset.length - 1];
- for (let i = 1; i <= remaining; i++) {
- const offsetProgress = motionUtils.progress(0, remaining, i);
- offset.push(mixNumber(min, 1, offsetProgress));
- }
- }
- function defaultOffset(arr) {
- const offset = [0];
- fillOffset(offset, arr.length - 1);
- return offset;
- }
- const isMotionValue = (value) => Boolean(value && value.getVelocity);
- function isDOMKeyframes(keyframes) {
- return typeof keyframes === "object" && !Array.isArray(keyframes);
- }
- function resolveSubjects(subject, keyframes, scope, selectorCache) {
- if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
- return motionDom.resolveElements(subject, scope, selectorCache);
- }
- else if (subject instanceof NodeList) {
- return Array.from(subject);
- }
- else if (Array.isArray(subject)) {
- return subject;
- }
- else {
- return [subject];
- }
- }
- function calculateRepeatDuration(duration, repeat, _repeatDelay) {
- return duration * (repeat + 1);
- }
- /**
- * Given a absolute or relative time definition and current/prev time state of the sequence,
- * calculate an absolute time for the next keyframes.
- */
- function calcNextTime(current, next, prev, labels) {
- if (typeof next === "number") {
- return next;
- }
- else if (next.startsWith("-") || next.startsWith("+")) {
- return Math.max(0, current + parseFloat(next));
- }
- else if (next === "<") {
- return prev;
- }
- else {
- return labels.get(next) ?? current;
- }
- }
- function eraseKeyframes(sequence, startTime, endTime) {
- for (let i = 0; i < sequence.length; i++) {
- const keyframe = sequence[i];
- if (keyframe.at > startTime && keyframe.at < endTime) {
- motionUtils.removeItem(sequence, keyframe);
- // If we remove this item we have to push the pointer back one
- i--;
- }
- }
- }
- function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
- /**
- * Erase every existing value between currentTime and targetTime,
- * this will essentially splice this timeline into any currently
- * defined ones.
- */
- eraseKeyframes(sequence, startTime, endTime);
- for (let i = 0; i < keyframes.length; i++) {
- sequence.push({
- value: keyframes[i],
- at: mixNumber(startTime, endTime, offset[i]),
- easing: getEasingForSegment(easing, i),
- });
- }
- }
- /**
- * Take an array of times that represent repeated keyframes. For instance
- * if we have original times of [0, 0.5, 1] then our repeated times will
- * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
- * down to a 0-1 scale.
- */
- function normalizeTimes(times, repeat) {
- for (let i = 0; i < times.length; i++) {
- times[i] = times[i] / (repeat + 1);
- }
- }
- function compareByTime(a, b) {
- if (a.at === b.at) {
- if (a.value === null)
- return 1;
- if (b.value === null)
- return -1;
- return 0;
- }
- else {
- return a.at - b.at;
- }
- }
- const defaultSegmentEasing = "easeInOut";
- const MAX_REPEAT = 20;
- function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
- const defaultDuration = defaultTransition.duration || 0.3;
- const animationDefinitions = new Map();
- const sequences = new Map();
- const elementCache = {};
- const timeLabels = new Map();
- let prevTime = 0;
- let currentTime = 0;
- let totalDuration = 0;
- /**
- * Build the timeline by mapping over the sequence 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 < sequence.length; i++) {
- const segment = sequence[i];
- /**
- * If this is a timeline label, mark it and skip the rest of this iteration.
- */
- if (typeof segment === "string") {
- timeLabels.set(segment, currentTime);
- continue;
- }
- else if (!Array.isArray(segment)) {
- timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
- continue;
- }
- let [subject, keyframes, transition = {}] = segment;
- /**
- * If a relative or absolute time value has been specified we need to resolve
- * it in relation to the currentTime.
- */
- if (transition.at !== undefined) {
- currentTime = calcNextTime(currentTime, transition.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;
- const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
- const valueKeyframesAsList = keyframesAsList(valueKeyframes);
- const { delay = 0, times = defaultOffset(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
- let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
- /**
- * Resolve stagger() if defined.
- */
- const calculatedDelay = typeof delay === "function"
- ? delay(elementIndex, numSubjects)
- : delay;
- /**
- * If this animation should and can use a spring, generate a spring easing function.
- */
- const numKeyframes = valueKeyframesAsList.length;
- const createGenerator = motionDom.isGenerator(type)
- ? type
- : generators?.[type];
- if (numKeyframes <= 2 && createGenerator) {
- /**
- * As we're creating an easing function from a spring,
- * ideally we want to generate it using the real distance
- * between the two keyframes. However this isn't always
- * possible - in these situations we use 0-100.
- */
- let absoluteDelta = 100;
- if (numKeyframes === 2 &&
- isNumberKeyframesArray(valueKeyframesAsList)) {
- const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
- absoluteDelta = Math.abs(delta);
- }
- const springTransition = { ...remainingTransition };
- if (duration !== undefined) {
- springTransition.duration = motionUtils.secondsToMilliseconds(duration);
- }
- const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
- ease = springEasing.ease;
- duration = springEasing.duration;
- }
- duration ?? (duration = defaultDuration);
- const startTime = currentTime + calculatedDelay;
- /**
- * If there's only one time offset of 0, fill in a second with length 1
- */
- if (times.length === 1 && times[0] === 0) {
- times[1] = 1;
- }
- /**
- * Fill out if offset if fewer offsets than keyframes
- */
- const remainder = times.length - valueKeyframesAsList.length;
- remainder > 0 && fillOffset(times, 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.
- */
- valueKeyframesAsList.length === 1 &&
- valueKeyframesAsList.unshift(null);
- /**
- * Handle repeat options
- */
- if (repeat) {
- motionUtils.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20");
- duration = calculateRepeatDuration(duration, repeat);
- const originalKeyframes = [...valueKeyframesAsList];
- const originalTimes = [...times];
- ease = Array.isArray(ease) ? [...ease] : [ease];
- const originalEase = [...ease];
- for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
- valueKeyframesAsList.push(...originalKeyframes);
- for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
- times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
- ease.push(keyframeIndex === 0
- ? "linear"
- : getEasingForSegment(originalEase, keyframeIndex - 1));
- }
- }
- normalizeTimes(times, repeat);
- }
- const targetTime = startTime + duration;
- /**
- * Add keyframes, mapping offsets to absolute time.
- */
- addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
- maxDuration = Math.max(calculatedDelay + duration, maxDuration);
- totalDuration = Math.max(targetTime, totalDuration);
- };
- if (isMotionValue(subject)) {
- const subjectSequence = getSubjectSequence(subject, sequences);
- resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
- }
- else {
- const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
- const numSubjects = subjects.length;
- /**
- * For every element in this segment, process the defined values.
- */
- for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
- /**
- * Cast necessary, but we know these are of this type
- */
- keyframes = keyframes;
- transition = transition;
- const thisSubject = subjects[subjectIndex];
- const subjectSequence = getSubjectSequence(thisSubject, sequences);
- for (const key in keyframes) {
- resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
- }
- }
- }
- prevTime = currentTime;
- currentTime += maxDuration;
- }
- /**
- * For every element and value combination create a new animation.
- */
- sequences.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(motionUtils.progress(0, totalDuration, at));
- valueEasing.push(easing || "easeOut");
- }
- /**
- * If the first keyframe doesn't land on offset: 0
- * provide one by duplicating the initial keyframe. This ensures
- * it snaps to the first keyframe when the animation starts.
- */
- if (valueOffset[0] !== 0) {
- valueOffset.unshift(0);
- keyframes.unshift(keyframes[0]);
- valueEasing.unshift(defaultSegmentEasing);
- }
- /**
- * If the last keyframe doesn't land on offset: 1
- * 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);
- }
- if (!animationDefinitions.has(element)) {
- animationDefinitions.set(element, {
- keyframes: {},
- transition: {},
- });
- }
- const definition = animationDefinitions.get(element);
- definition.keyframes[key] = keyframes;
- definition.transition[key] = {
- ...defaultTransition,
- duration: totalDuration,
- ease: valueEasing,
- times: valueOffset,
- ...sequenceTransition,
- };
- }
- });
- return animationDefinitions;
- }
- function getSubjectSequence(subject, sequences) {
- !sequences.has(subject) && sequences.set(subject, {});
- return sequences.get(subject);
- }
- function getValueSequence(name, sequences) {
- if (!sequences[name])
- sequences[name] = [];
- return sequences[name];
- }
- function keyframesAsList(keyframes) {
- return Array.isArray(keyframes) ? keyframes : [keyframes];
- }
- function getValueTransition(transition, key) {
- return transition && transition[key]
- ? {
- ...transition,
- ...transition[key],
- }
- : { ...transition };
- }
- const isNumber = (keyframe) => typeof keyframe === "number";
- const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
- function animateElements(elementOrSelector, keyframes, options, scope) {
- const elements = motionDom.resolveElements(elementOrSelector, scope);
- const numElements = elements.length;
- motionUtils.invariant(Boolean(numElements), "No valid element provided.");
- const animations = [];
- for (let i = 0; i < numElements; i++) {
- const element = elements[i];
- const elementTransition = { ...options };
- /**
- * Resolve stagger function if provided.
- */
- if (typeof elementTransition.delay === "function") {
- elementTransition.delay = elementTransition.delay(i, numElements);
- }
- for (const valueName in keyframes) {
- const valueKeyframes = keyframes[valueName];
- const valueOptions = {
- ...motionDom.getValueTransition(elementTransition, valueName),
- };
- valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
- valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
- animations.push(new motionDom.NativeAnimation({
- element,
- name: valueName,
- keyframes: valueKeyframes,
- transition: valueOptions,
- allowFlatten: !elementTransition.type && !elementTransition.ease,
- }));
- }
- }
- return animations;
- }
- function animateSequence(definition, options) {
- const animations = [];
- createAnimationsFromSequence(definition, options).forEach(({ keyframes, transition }, element) => {
- animations.push(...animateElements(element, keyframes, transition));
- });
- return new motionDom.GroupAnimationWithThen(animations);
- }
- const createScopedWaapiAnimate = (scope) => {
- function scopedAnimate(elementOrSelector, keyframes, options) {
- return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
- }
- return scopedAnimate;
- };
- const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
- exports.animate = animateMini;
- exports.animateSequence = animateSequence;
|