diff --git a/packages/framer-motion/src/animation/sequence/__tests__/index.test.ts b/packages/framer-motion/src/animation/sequence/__tests__/index.test.ts index c47d885c10..c568d8515a 100644 --- a/packages/framer-motion/src/animation/sequence/__tests__/index.test.ts +++ b/packages/framer-motion/src/animation/sequence/__tests__/index.test.ts @@ -730,6 +730,29 @@ describe("createAnimationsFromSequence", () => { expect(transition.y.times).toEqual([0, 0.5, 1]) }) + test("It passes repeat: Infinity through to the final transition (#2915)", () => { + const animations = createAnimationsFromSequence( + [ + [ + a, + { x: [0, 100] }, + { duration: 1, repeat: Infinity, ease: "linear" }, + ], + ], + undefined, + undefined, + { spring } + ) + + expect(animations.get(a)!.keyframes.x).toEqual([0, 100]) + const { duration, times, ease, repeat } = + animations.get(a)!.transition.x + expect(duration).toEqual(1) + expect(times).toEqual([0, 1]) + expect(ease).toEqual(["linear", "linear"]) + expect(repeat).toEqual(Infinity) + }) + test.skip("It correctly adds repeatDelay between repeated keyframes", () => { const animations = createAnimationsFromSequence( [ diff --git a/packages/framer-motion/src/animation/sequence/create.ts b/packages/framer-motion/src/animation/sequence/create.ts index 07ead3c85b..07a2b26036 100644 --- a/packages/framer-motion/src/animation/sequence/create.ts +++ b/packages/framer-motion/src/animation/sequence/create.ts @@ -16,7 +16,6 @@ import { import { Easing, getEasingForSegment, - invariant, progress, secondsToMilliseconds, } from "motion-utils" @@ -51,6 +50,16 @@ export function createAnimationsFromSequence( const elementCache = {} const timeLabels = new Map() + /** + * Store per-value repeat options that can't be expanded into keyframes + * (e.g. repeat: Infinity) and need to be passed through to the + * final transition for the animation engine to handle. + */ + const repeatPassthrough = new Map< + ValueSequence, + Pick + >() + let prevTime = 0 let currentTime = 0 let totalDuration = 0 @@ -198,46 +207,58 @@ export function createAnimationsFromSequence( * Handle repeat options */ if (repeat) { - invariant( - repeat < MAX_REPEAT, - "Repeat count too high, must be less than 20", - "repeat-count-high" - ) - - duration = calculateRepeatDuration( - duration, - repeat, - repeatDelay - ) - - const originalKeyframes = [...valueKeyframesAsList] - const originalTimes = [...times] - ease = Array.isArray(ease) ? [...ease] : [ease] - const originalEase = [...ease] + if (repeat >= MAX_REPEAT) { + /** + * For large/infinite repeat counts, don't expand keyframes. + * Pass repeat options through to the final transition + * and let the animation engine handle repeating. + */ + repeatPassthrough.set(valueSequence, { + repeat, + repeatType: repeatType as Transition["repeatType"], + repeatDelay: repeatDelay || undefined, + }) + } else { + duration = calculateRepeatDuration( + duration, + repeat, + repeatDelay + ) - for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) { - valueKeyframesAsList.push(...originalKeyframes) + const originalKeyframes = [...valueKeyframesAsList] + const originalTimes = [...times] + ease = Array.isArray(ease) ? [...ease] : [ease] + const originalEase = [...ease] for ( - let keyframeIndex = 0; - keyframeIndex < originalKeyframes.length; - keyframeIndex++ + let repeatIndex = 0; + repeatIndex < repeat; + repeatIndex++ ) { - times.push( - originalTimes[keyframeIndex] + (repeatIndex + 1) - ) - ease.push( - keyframeIndex === 0 - ? "linear" - : getEasingForSegment( - originalEase, - keyframeIndex - 1 - ) - ) + 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) + normalizeTimes(times, repeat) + } } const targetTime = startTime + duration @@ -386,6 +407,7 @@ export function createAnimationsFromSequence( ease: valueEasing, times: valueOffset, ...sequenceTransition, + ...repeatPassthrough.get(valueSequence), } } })