Skip to content

Commit

Permalink
Resume optimised animations where possible (#2774)
Browse files Browse the repository at this point in the history
* Ensure values cancelled on scroll

* Fixing

* Latest

* Remove logging

* Resume selected optimised animations

* Updating

* Latest

* Fixing
  • Loading branch information
mattgperry authored Aug 29, 2024
1 parent 689b247 commit 45a1d06
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 29 deletions.
4 changes: 2 additions & 2 deletions dev/html/public/optimized-appear/defer-handoff-block.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
const { matchViewportBox } = window.Assert
const root = document.getElementById("root")

const duration = 4
const duration = 1
const x = motionValue(0)
const xTarget = 500

Expand Down Expand Up @@ -85,7 +85,7 @@
)

const startTime = performance.now()
while (performance.now() - startTime < 1000) {}
while (performance.now() - startTime < 200) {}
})

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,12 @@
// Cypress browser doesn't support getAnimations
if (!optimisedBox.getAnimations) return

console.log(
optimisedBox.getAnimations().length,
layoutBox.getAnimations().length
)

// TODO: Uncomment with follow up PR to resume cancelled WAAPI animations
// if (optimisedBox.getAnimations().length !== 1) {
// showError(
// optimisedBox,
// `Optimised parent should have resumed WAAPI animation`
// )
// }
if (optimisedBox.getAnimations().length !== 1) {
showError(
optimisedBox,
`Optimised parent should have resumed WAAPI animation`
)
}

if (layoutBox.getAnimations().length !== 0) {
showError(
Expand Down
24 changes: 14 additions & 10 deletions packages/framer-motion/src/animation/optimized-appear/handoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ export function handoffOptimizedAppearAnimation(

const { animation, startTime } = optimisedAnimation

function cancelAnimation() {
window.MotionCancelOptimisedAnimation?.(elementId, valueName, frame)
}

/**
* We can cancel the animation once it's finished now that we've synced
* with Motion.
*
* Prefer onfinish over finished as onfinish is backwards compatible with
* older browsers.
*/
animation.onfinish = cancelAnimation

if (startTime === null || window.MotionHandoffIsComplete) {
/**
* If the startTime is null, this animation is the Paint Ready detection animation
Expand All @@ -24,16 +37,7 @@ export function handoffOptimizedAppearAnimation(
* Or if we've already handed off the animation then we're now interrupting it.
* In which case we need to cancel it.
*/
appearAnimationStore.delete(storeId)

frame.render(() =>
frame.render(() => {
try {
animation.cancel()
} catch (error) {}
})
)

cancelAnimation()
return null
} else {
return startTime
Expand Down
54 changes: 51 additions & 3 deletions packages/framer-motion/src/animation/optimized-appear/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import { animateStyle } from "../animators/waapi"
import { NativeAnimationOptions } from "../animators/waapi/types"
import { optimizedAppearDataId } from "./data-id"
import { handoffOptimizedAppearAnimation } from "./handoff"
import { appearAnimationStore, elementsWithAppearAnimations } from "./store"
import {
appearAnimationStore,
AppearStoreEntry,
elementsWithAppearAnimations,
} from "./store"
import { noop } from "../../utils/noop"
import "./types"
import { getOptimisedAppearId } from "./get-appear-id"
import { MotionValue } from "../../value"
import type { WithAppearProps } from "./types"
import { Batcher } from "../../frameloop/types"

/**
* A single time to use across all animations to manually set startTime
Expand All @@ -25,6 +30,20 @@ let startFrameTime: number
*/
let readyAnimation: Animation

/**
* Keep track of animations that were suspended vs cancelled so we
* can easily resume them when we're done measuring layout.
*/
const suspendedAnimations = new Set<AppearStoreEntry>()

function resumeSuspendedAnimations() {
suspendedAnimations.forEach((data) => {
data.animation.play()
data.animation.startTime = data.startTime
})
suspendedAnimations.clear()
}

export function startOptimizedAppearAnimation(
element: HTMLElement,
name: string,
Expand Down Expand Up @@ -98,14 +117,43 @@ export function startOptimizedAppearAnimation(
*/
window.MotionCancelOptimisedAnimation = (
elementId: string,
valueName: string
valueName: string,
frame?: Batcher,
canResume?: boolean
) => {
const animationId = appearStoreId(elementId, valueName)
const data = appearAnimationStore.get(animationId)

if (data) {
if (!data) return

if (frame && canResume === undefined) {
/**
* Wait until the end of the subsequent frame to cancel the animation
* to ensure we don't remove the animation before the main thread has
* had a chance to resolve keyframes and render.
*/
frame.postRender(() => {
frame.postRender(() => {
data.animation.cancel()
})
})
} else {
data.animation.cancel()
}

if (frame && canResume) {
suspendedAnimations.add(data)
frame.render(resumeSuspendedAnimations)
} else {
appearAnimationStore.delete(animationId)

/**
* If there are no more animations left, we can remove the cancel function.
* This will let us know when we can stop checking for conflicting layout animations.
*/
if (!appearAnimationStore.size) {
window.MotionCancelOptimisedAnimation = undefined
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ declare global {
) => boolean
MotionCancelOptimisedAnimation?: (
elementId?: string,
valueName?: string
valueName?: string,
frame?: Batcher,
canResume?: boolean
) => void
MotionCheckAppearSync?: (
visualElement: WithAppearProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@ function cancelTreeOptimisedTransformAnimations(
const appearId = getOptimisedAppearId(visualElement)

if (window.MotionHasOptimisedAnimation!(appearId, "transform")) {
window.MotionCancelOptimisedAnimation!(appearId, "transform")
const { layout, layoutId } = projectionNode.options
window.MotionCancelOptimisedAnimation!(
appearId,
"transform",
frame,
!(layout || layoutId)
)
}

const { parent } = projectionNode
Expand Down

0 comments on commit 45a1d06

Please sign in to comment.