Skip to content

Commit

Permalink
Merge pull request #2468 from framer/fix/repeat-type-final-keyframe
Browse files Browse the repository at this point in the history
Fixing application of final keyframe with repeatType mirror and reverse
  • Loading branch information
mergetron[bot] authored Jan 4, 2024
2 parents 4785efa + 60de314 commit 1b297bb
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 4 deletions.
2 changes: 1 addition & 1 deletion packages/framer-motion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"clean": "rm -rf types dist lib",
"test": "yarn test-server && yarn test-client",
"test-ci": "yarn test",
"test-client": "jest --config jest.config.json --max-workers=2",
"test-client": "jest --config jest.config.json --max-workers=2 animate-prop",
"test-server": "jest --config jest.config.ssr.json ",
"test-watch": "jest --watch --coverage --coverageReporters=lcov --config jest.config.json",
"test-appear": "yarn run collect-appear-tests && start-server-and-test 'pushd ../../; python -m SimpleHTTPServer; popd' http://0.0.0.0:8000 'cypress run -s cypress/integration/appear.chrome.ts --config baseUrl=http://localhost:8000/'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,61 @@ describe("animate", () => {
})
})

test("Correctly applies repeat type 'loop'", async () => {
// Large currentTime
const animation = animateValue({
keyframes: [0, 100],
duration: 100,
repeat: 1,
repeatType: "loop",
})

expect(animation.sample(201).value).toEqual(100)

// Odd repeat animation
await new Promise<void>((resolve) => {
testAnimate(
{
keyframes: [0, 100],
ease: "linear",
repeat: 1,
repeatType: "loop",
},
[0, 20, 40, 60, 80, 100, 20, 40, 60, 80, 100],
resolve
)
})

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

test("Correctly applies repeat type 'reverse'", async () => {
// Large currentTime
const animation = animateValue({
keyframes: [0, 100],
duration: 100,
repeat: 1,
repeatType: "reverse",
})

expect(animation.sample(201).value).toEqual(0)

// Odd repeat animation
await new Promise<void>((resolve) => {
testAnimate(
{
Expand All @@ -250,6 +304,7 @@ describe("animate", () => {
)
})

// Even repeat animation
return new Promise<void>((resolve) => {
testAnimate(
{
Expand All @@ -268,6 +323,16 @@ describe("animate", () => {
})

test("Correctly applies repeat type 'mirror'", async () => {
// Large currentTime
const animation = animateValue({
keyframes: [0, 100],
duration: 100,
repeat: 1,
repeatType: "mirror",
})

expect(animation.sample(201).value).toEqual(0)

await new Promise<void>((resolve) => {
testAnimate(
{
Expand Down
2 changes: 1 addition & 1 deletion packages/framer-motion/src/animation/animators/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export function animateValue<V = number>({
let p = clamp(0, 1, iterationProgress)

if (currentTime > totalDuration) {
p = repeatType === "reverse" && iterationIsOdd ? 1 : 0
p = repeatType === "reverse" && Boolean(repeat % 2) ? 0 : 1
}

elapsed = p * resolvedDuration
Expand Down
163 changes: 163 additions & 0 deletions packages/framer-motion/src/motion/__tests__/animate-prop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,169 @@ describe("animate prop as object", () => {
})
return expect(promise).resolves.toBe(20)
})

test("Correctly applies final keyframe with repeatType reverse and odd numbered repeat", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
const Component = () => (
<motion.div
animate={{ x: [0, 20] }}
transition={{
x: {
type: "tween",
duration: 0.1,
repeatDelay: 0.1,
repeat: 1,
repeatType: "reverse",
},
}}
onAnimationComplete={() => {
frame.postRender(() => resolve(x.get()))
}}
style={{ x }}
/>
)
const { rerender } = render(<Component />)
rerender(<Component />)
})
return expect(promise).resolves.toBe(0)
})

test.only("Correctly applies final keyframe with repeatType mirror and odd numbered repeat", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
const Component = () => (
<motion.div
animate={{ x: [0, 20] }}
transition={{
x: {
type: "tween",
duration: 0.1,
repeatDelay: 0.1,
repeat: 1,
repeatType: "mirror",
},
}}
onAnimationComplete={() => {
frame.postRender(() => resolve(x.get()))
}}
style={{ x }}
/>
)
const { rerender } = render(<Component />)
rerender(<Component />)
})
return expect(promise).resolves.toBe(0)
})

test("Correctly applies final keyframe with repeatType loop and odd numbered repeat", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
const Component = () => (
<motion.div
animate={{ x: [0, 20] }}
transition={{
x: {
type: "tween",
duration: 0.1,
repeatDelay: 0.1,
repeat: 1,
repeatType: "loop",
},
}}
onAnimationComplete={() => {
frame.postRender(() => resolve(x.get()))
}}
style={{ x }}
/>
)
const { rerender } = render(<Component />)
rerender(<Component />)
})
return expect(promise).resolves.toBe(20)
})

test("Correctly applies final keyframe with repeatType reverse and even numbered repeat", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
const Component = () => (
<motion.div
animate={{ x: [0, 20] }}
transition={{
x: {
type: "tween",
duration: 0.1,
repeatDelay: 0.1,
repeat: 2,
repeatType: "reverse",
},
}}
onAnimationComplete={() => {
frame.postRender(() => resolve(x.get()))
}}
style={{ x }}
/>
)
const { rerender } = render(<Component />)
rerender(<Component />)
})
return expect(promise).resolves.toBe(20)
})

test("Correctly applies final keyframe with repeatType mirror and even numbered repeat", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
const Component = () => (
<motion.div
animate={{ x: [0, 20] }}
transition={{
x: {
type: "tween",
duration: 0.1,
repeatDelay: 0.1,
repeat: 2,
repeatType: "mirror",
},
}}
onAnimationComplete={() => {
frame.postRender(() => resolve(x.get()))
}}
style={{ x }}
/>
)
const { rerender } = render(<Component />)
rerender(<Component />)
})
return expect(promise).resolves.toBe(20)
})

test("Correctly applies final keyframe with repeatType loop and even numbered repeat", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
const Component = () => (
<motion.div
animate={{ x: [0, 20] }}
transition={{
x: {
type: "tween",
duration: 0.1,
repeatDelay: 0.1,
repeat: 2,
repeatType: "loop",
},
}}
onAnimationComplete={() => {
frame.postRender(() => resolve(x.get()))
}}
style={{ x }}
/>
)
const { rerender } = render(<Component />)
rerender(<Component />)
})
return expect(promise).resolves.toBe(20)
})

test("animates previously unseen properties", () => {
const Component = ({ animate }: any) => (
<motion.div animate={animate} transition={{ type: false }} />
Expand Down
2 changes: 0 additions & 2 deletions packages/framer-motion/src/render/dom/scroll/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ function updateAxisInfo(
elapsed > maxElapsed
? 0
: velocityPerSecond(axis.current - prev, elapsed)

if (axisName === "y") console.log(axis)
}

export function updateScrollInfo(
Expand Down

0 comments on commit 1b297bb

Please sign in to comment.