Skip to content

Commit

Permalink
Fixing final keyframe application when animation reversed (#2028)
Browse files Browse the repository at this point in the history
* Adding failing test

* Fixing iteration count

* Fixing tests

* Updating tests

* Update
  • Loading branch information
mattgperry authored Mar 17, 2023
1 parent 6d3df23 commit f11408e
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Undocumented APIs should be considered internal and may change without warning.
### Fixed

- `.stop()` stops animations permanently.
- `useSpring` timing.
- `animate()` with `repeat: 1` and `repeatType` `"reverse"` or `"mirror"` correctly applies final keyframe.

## [10.5.0] 2023-03-16

Expand Down
52 changes: 52 additions & 0 deletions packages/framer-motion/src/animation/__tests__/animate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,58 @@ describe("animate", () => {
})
})

test("Applies final target keyframe when animation has finished, repeat: reverse", async () => {
const div = document.createElement("div")
const animation = animate(
div,
{ opacity: [0.2, 0.5] },
{
duration,
repeat: 1,
repeatType: "reverse",
}
)
await animation.then(() => {
expect(div).toHaveStyle("opacity: 0.2")
})
})

test("Applies final target keyframe when animation has finished, repeat: reverse even", async () => {
const div = document.createElement("div")
const animation = animate(
div,
{ opacity: [0.2, 0.5] },
{ duration, repeat: 2, repeatType: "reverse" }
)
await animation.then(() => {
expect(div).toHaveStyle("opacity: 0.5")
})
})

test("Applies final target keyframe when animation has finished, repeat: mirror", async () => {
const div = document.createElement("div")
const animation = animate(
div,
{ opacity: [0.2, 0.5] },
{ duration, repeat: 1, repeatType: "mirror" }
)
await animation.then(() => {
expect(div).toHaveStyle("opacity: 0.2")
})
})

test("Applies final target keyframe when animation has finished, repeat: mirror even", async () => {
const div = document.createElement("div")
const animation = animate(
div,
{ opacity: [0.2, 0.5] },
{ duration, repeat: 1, repeatType: "mirror" }
)
await animation.then(() => {
expect(div).toHaveStyle("opacity: 0.2")
})
})

test("time sets and gets time", async () => {
const div = document.createElement("div")
const animation = animate(div, { x: 100 }, { duration: 10 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ describe("animate", () => {
})

test("Correctly applies repeat type 'reverse'", async () => {
return new Promise<void>((resolve) => {
await new Promise<void>((resolve) => {
testAnimate(
{
keyframes: [0, 100],
Expand All @@ -249,10 +249,26 @@ describe("animate", () => {
resolve
)
})

return new Promise<void>((resolve) => {
testAnimate(
{
keyframes: [0, 100],
ease: "linear",
repeat: 2,
repeatType: "reverse",
},
[
0, 20, 40, 60, 80, 100, 80, 60, 40, 20, 0, 20, 40, 60, 80,
100,
],
resolve
)
})
})

test("Correctly applies repeat type 'mirror'", async () => {
return new Promise<void>((resolve) => {
await new Promise<void>((resolve) => {
testAnimate(
{
keyframes: [0, 100],
Expand All @@ -264,6 +280,19 @@ describe("animate", () => {
resolve
)
})

return new Promise<void>((resolve) => {
testAnimate(
{
keyframes: [0, 100],
repeat: 2,
ease: reverseEasing((v) => v * v),
repeatType: "mirror",
},
[0, 36, 64, 84, 96, 100, 64, 36, 16, 4, 0, 36, 64, 84, 96, 100],
resolve
)
})
})

test("Correctly applies repeatDelay", async () => {
Expand Down
17 changes: 10 additions & 7 deletions packages/framer-motion/src/animation/animators/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,16 @@ export function animateValue<V = number>({
if (!iterationProgress && progress >= 1) {
iterationProgress = 1
}

iterationProgress === 1 && currentIteration--

currentIteration = Math.min(currentIteration, repeat + 1)

/**
* Reverse progress if we're not running in "normal" direction
*/
const iterationIsOdd = currentIteration % 2
const iterationIsOdd = Boolean(currentIteration % 2)

if (iterationIsOdd) {
if (repeatType === "reverse") {
iterationProgress = 1 - iterationProgress
Expand All @@ -216,12 +220,11 @@ export function animateValue<V = number>({
}
}

const p =
time >= totalDuration
? repeatType === "reverse" && iterationIsOdd
? 0
: 1
: clamp(0, 1, iterationProgress)
let p = clamp(0, 1, iterationProgress)

if (time > totalDuration) {
p = repeatType === "reverse" && iterationIsOdd ? 1 : 0
}

elapsed = p * resolvedDuration
}
Expand Down

0 comments on commit f11408e

Please sign in to comment.