diff --git a/dev/optimized-appear/resync.html b/dev/optimized-appear/resync.html
index b2902b08e8..ad0dfb366a 100644
--- a/dev/optimized-appear/resync.html
+++ b/dev/optimized-appear/resync.html
@@ -68,15 +68,16 @@
*/
onAnimationStart: () => {
frame.postRender(() => {
- const box = document.getElementById("box")
+ frame.postRender(() => {
+ const box = document.getElementById("box")
- if (!box) return
+ if (!box) return
- const { opacity } = window.getComputedStyle(box)
-
- if (parseFloat(opacity) < 0.7) {
- showError(box, "Resync failed")
- }
+ const { opacity } = window.getComputedStyle(box)
+ if (parseFloat(opacity) < 0.65) {
+ showError(box, "Resync failed")
+ }
+ })
})
},
[optimizedAppearDataAttribute]: "a",
diff --git a/packages/framer-motion/src/animation/animators/waapi/create-accelerated-animation.ts b/packages/framer-motion/src/animation/animators/waapi/create-accelerated-animation.ts
index 7f39304b51..e8c923acb9 100644
--- a/packages/framer-motion/src/animation/animators/waapi/create-accelerated-animation.ts
+++ b/packages/framer-motion/src/animation/animators/waapi/create-accelerated-animation.ts
@@ -1,5 +1,5 @@
import { EasingDefinition } from "../../../easing/types"
-import { frame, cancelFrame } from "../../../frameloop"
+import { frame, cancelFrame, frameData } from "../../../frameloop"
import type { VisualElement } from "../../../render/VisualElement"
import type { MotionValue } from "../../../value"
import { AnimationPlaybackControls, ValueAnimationOptions } from "../../types"
@@ -136,6 +136,18 @@ export function createAcceleratedAnimation(
}
)
+ /**
+ * WAAPI animations don't resolve startTime synchronously. But a blocked
+ * thread could delay the startTime resolution by a noticeable amount.
+ * For synching handoff animations with the new Motion animation we want
+ * to ensure startTime is synchronously set.
+ */
+ if (options.syncStart) {
+ animation.startTime = frameData.isProcessing
+ ? frameData.timestamp
+ : performance.now()
+ }
+
const cancelAnimation = () => animation.cancel()
const safeCancel = () => {
diff --git a/packages/framer-motion/src/animation/interfaces/visual-element-target.ts b/packages/framer-motion/src/animation/interfaces/visual-element-target.ts
index dfcaec86cf..7f27fc44bd 100644
--- a/packages/framer-motion/src/animation/interfaces/visual-element-target.ts
+++ b/packages/framer-motion/src/animation/interfaces/visual-element-target.ts
@@ -2,7 +2,7 @@ import { frame } from "../../frameloop"
import { transformProps } from "../../render/html/utils/transform"
import type { AnimationTypeState } from "../../render/utils/animation-state"
import type { VisualElement } from "../../render/VisualElement"
-import type { TargetAndTransition } from "../../types"
+import type { TargetAndTransition, Transition } from "../../types"
import { optimizedAppearDataAttribute } from "../optimized-appear/data-id"
import type { VisualElementAnimationOptions } from "./types"
import { animateMotionValue } from "./motion-value"
@@ -62,7 +62,12 @@ export function animateTarget(
continue
}
- const valueTransition = { delay, elapsed: 0, ...transition }
+ const valueTransition = {
+ delay,
+ syncStart: false,
+ elapsed: 0,
+ ...transition,
+ }
/**
* If this is the first time a value is being animated, check
@@ -79,6 +84,7 @@ export function animateTarget(
value,
frame
)
+ valueTransition.syncStart = true
}
}
diff --git a/packages/framer-motion/src/animation/types.ts b/packages/framer-motion/src/animation/types.ts
index 8ad1026ac9..79878f674c 100644
--- a/packages/framer-motion/src/animation/types.ts
+++ b/packages/framer-motion/src/animation/types.ts
@@ -25,6 +25,7 @@ export interface Transition
type?: "decay" | "spring" | "keyframes" | "tween" | "inertia"
duration?: number
autoplay?: boolean
+ syncStart?: boolean
}
export interface ValueAnimationTransition