Skip to content

Commit e0890f2

Browse files
mattgperrymergatron[bot]
authored and
mergatron[bot]
committedMar 29, 2023
Adding duration prop
1 parent 55b2be9 commit e0890f2

File tree

10 files changed

+143
-20
lines changed

10 files changed

+143
-20
lines changed
 

‎packages/framer-motion/src/animation/GroupPlaybackControls.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AnimationPlaybackControls } from "./types"
22

3-
type PropNames = "time" | "speed"
3+
type PropNames = "time" | "speed" | "duration"
44

55
export class GroupPlaybackControls implements AnimationPlaybackControls {
66
animations: AnimationPlaybackControls[]
@@ -44,6 +44,14 @@ export class GroupPlaybackControls implements AnimationPlaybackControls {
4444
this.setAll("speed", speed)
4545
}
4646

47+
get duration() {
48+
let max = 0
49+
for (let i = 0; i < this.animations.length; i++) {
50+
max = Math.max(max, this.animations[i].duration)
51+
}
52+
return max
53+
}
54+
4755
private runAll(
4856
methodName: keyof Omit<AnimationPlaybackControls, PropNames | "then">
4957
) {

‎packages/framer-motion/src/animation/__tests__/GroupPlaybackControls.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ function createTestAnimationControls(
77
return {
88
time: 1,
99
speed: 1,
10+
duration: 10,
1011
stop: () => {},
1112
play: () => {},
1213
pause: () => {},
@@ -193,4 +194,20 @@ describe("GroupPlaybackControls", () => {
193194
expect(a.speed).toEqual(-1)
194195
expect(b.speed).toEqual(-1)
195196
})
197+
198+
test("Gets max duration", async () => {
199+
const a = createTestAnimationControls({
200+
duration: 3,
201+
})
202+
const b = createTestAnimationControls({
203+
duration: 2,
204+
})
205+
const c = createTestAnimationControls({
206+
duration: 1,
207+
})
208+
209+
const controls = new GroupPlaybackControls([a, b, c])
210+
211+
expect(controls.duration).toEqual(3)
212+
})
196213
})

‎packages/framer-motion/src/animation/__tests__/animate-waapi.test.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ describe("animate() with WAAPI", () => {
118118
)
119119
})
120120

121+
test("Returns duration correctly", async () => {
122+
const animation = animate(
123+
document.createElement("div"),
124+
{ opacity: 1 },
125+
{ duration: 2, opacity: { duration: 3 } }
126+
)
127+
expect(animation.duration).toEqual(3)
128+
})
129+
121130
test("Can accept timeline sequences", async () => {
122131
const a = document.createElement("div")
123132
const b = document.createElement("div")

‎packages/framer-motion/src/animation/animators/__tests__/instant.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,18 @@ describe("instantAnimation", () => {
7474
expect(onUpdate).toBeCalledWith(1)
7575
expect(onComplete).toBeCalled()
7676
})
77+
78+
test("Returns duration: 0", async () => {
79+
const animation = createInstantAnimation({
80+
delay: 0,
81+
keyframes: [0, 1],
82+
})
83+
expect(animation.duration).toEqual(0)
84+
85+
const animationWithDelay = createInstantAnimation({
86+
delay: 0.2,
87+
keyframes: [0, 1],
88+
})
89+
expect(animationWithDelay.duration).toEqual(0)
90+
})
7791
})

‎packages/framer-motion/src/animation/animators/instant.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { noop } from "../../utils/noop"
44

55
export function createInstantAnimation<V>({
66
keyframes,
7-
delay: delayBy,
7+
delay,
88
onUpdate,
99
onComplete,
1010
}: ValueAnimationOptions<V>): AnimationPlaybackControls {
@@ -22,6 +22,7 @@ export function createInstantAnimation<V>({
2222
return {
2323
time: 0,
2424
speed: 1,
25+
duration: 0,
2526
play: noop<void>,
2627
pause: noop<void>,
2728
stop: noop<void>,
@@ -34,10 +35,11 @@ export function createInstantAnimation<V>({
3435
}
3536
}
3637

37-
return delayBy
38+
return delay
3839
? animateValue({
3940
keyframes: [0, 1],
40-
duration: delayBy,
41+
duration: 0,
42+
delay,
4143
onComplete: setValue,
4244
})
4345
: setValue()

‎packages/framer-motion/src/animation/animators/js/__tests__/animate.test.ts

+52
Original file line numberDiff line numberDiff line change
@@ -1242,4 +1242,56 @@ describe("animate", () => {
12421242

12431243
await animation
12441244
})
1245+
1246+
test("Correctly returns duration", async () => {
1247+
expect(
1248+
animateValue({
1249+
keyframes: [0, 100],
1250+
duration: 1000,
1251+
}).duration
1252+
).toEqual(1)
1253+
})
1254+
1255+
test("Correctly returns duration when delay is defined", () => {
1256+
expect(
1257+
animateValue({
1258+
keyframes: [0, 100],
1259+
delay: 1000,
1260+
duration: 1000,
1261+
}).duration
1262+
).toEqual(1)
1263+
})
1264+
1265+
test("Correctly returns duration when repeat is defined", () => {
1266+
expect(
1267+
animateValue({
1268+
keyframes: [0, 100],
1269+
delay: 1000,
1270+
duration: 1000,
1271+
repeat: Infinity,
1272+
}).duration
1273+
).toEqual(1)
1274+
})
1275+
1276+
test("Correctly returns duration when animation is spring", () => {
1277+
expect(
1278+
animateValue({
1279+
keyframes: [0, 100],
1280+
delay: 1000,
1281+
duration: 1000,
1282+
type: "spring",
1283+
}).duration
1284+
).toEqual(1)
1285+
})
1286+
1287+
test("Correctly returns duration when animation is dynamic spring", () => {
1288+
expect(
1289+
animateValue({
1290+
keyframes: [0, 100],
1291+
stiffness: 200,
1292+
damping: 10,
1293+
type: "spring",
1294+
}).duration
1295+
).toEqual(1.2)
1296+
})
12451297
})

‎packages/framer-motion/src/animation/animators/js/index.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ export function animateValue<V = number>({
158158
}
159159

160160
// Rebase on delay
161-
time = Math.max(time - delay, 0)
161+
const timeWithDelay = time - delay
162+
const isInDelayPhase = timeWithDelay < 0
163+
time = Math.max(timeWithDelay, 0)
162164

163165
/**
164166
* If this animation has finished, set the current time
@@ -229,15 +231,22 @@ export function animateValue<V = number>({
229231
elapsed = p * resolvedDuration
230232
}
231233

232-
const state = frameGenerator.next(elapsed)
234+
/**
235+
* If we're in negative time, set state as the initial keyframe.
236+
* This prevents delay: x, duration: 0 animations from finishing
237+
* instantly.
238+
*/
239+
const state = isInDelayPhase
240+
? { done: false, value: keyframes[0] }
241+
: frameGenerator.next(elapsed)
233242

234243
if (mapNumbersToKeyframes) {
235244
state.value = mapNumbersToKeyframes(state.value)
236245
}
237246

238247
let { done } = state
239248

240-
if (calculatedDuration !== null) {
249+
if (!isInDelayPhase && calculatedDuration !== null) {
241250
done = time >= totalDuration
242251
}
243252

@@ -323,6 +332,14 @@ export function animateValue<V = number>({
323332
startTime = animationDriver.now() - newTime / speed
324333
}
325334
},
335+
get duration() {
336+
const duration =
337+
generator.calculatedDuration === null
338+
? calculateDuration(generator)
339+
: generator.calculatedDuration
340+
341+
return millisecondsToSeconds(duration)
342+
},
326343
get speed() {
327344
return speed
328345
},

‎packages/framer-motion/src/animation/animators/waapi/create-accelerated-animation.ts

+3
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ export function createAcceleratedAnimation(
169169
set speed(newSpeed: number) {
170170
animation.playbackRate = newSpeed
171171
},
172+
get duration() {
173+
return millisecondsToSeconds(duration)
174+
},
172175
play: () => {
173176
if (hasStopped) return
174177
animation.play()

‎packages/framer-motion/src/animation/interfaces/motion-value.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,6 @@ export const animateMotionValue = (
7373
},
7474
}
7575

76-
if (
77-
!isOriginAnimatable ||
78-
!isTargetAnimatable ||
79-
instantAnimationState.current ||
80-
valueTransition.type === false
81-
) {
82-
/**
83-
* If we can't animate this value, or the global instant animation flag is set,
84-
* or this is simply defined as an instant transition, return an instant transition.
85-
*/
86-
return createInstantAnimation(options)
87-
}
88-
8976
/**
9077
* If there's no transition defined for this value, we can generate
9178
* unqiue transition settings for this value.
@@ -110,6 +97,19 @@ export const animateMotionValue = (
11097
options.repeatDelay = secondsToMilliseconds(options.repeatDelay)
11198
}
11299

100+
if (
101+
!isOriginAnimatable ||
102+
!isTargetAnimatable ||
103+
instantAnimationState.current ||
104+
valueTransition.type === false
105+
) {
106+
/**
107+
* If we can't animate this value, or the global instant animation flag is set,
108+
* or this is simply defined as an instant transition, return an instant transition.
109+
*/
110+
return createInstantAnimation(options)
111+
}
112+
113113
/**
114114
* Animate via WAAPI if possible.
115115
*/

‎packages/framer-motion/src/animation/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export type ElementOrSelector =
7474
export interface AnimationPlaybackControls {
7575
time: number
7676
speed: number
77+
duration: number
7778
stop: () => void
7879
play: () => void
7980
pause: () => void

0 commit comments

Comments
 (0)
Please sign in to comment.