UNPKG

framer-motion

Version:

An open source and production-ready motion library for React on the web

104 lines (88 loc) • 3.27 kB
import { createContext } from "react" import { VisualElement } from "../../render/types" import { Presence } from "./types" /** * Handlers for batching sync layout lifecycles. We batches these processes to cut * down on layout thrashing */ export interface SyncLayoutLifecycles { measureLayout: (child: VisualElement) => void layoutReady: (child: VisualElement) => void parent?: VisualElement } /** * The exposed API for children to add themselves to the batcher and to flush it. */ export interface SyncLayoutBatcher { add: (child: VisualElement) => void flush: (handler?: SyncLayoutLifecycles) => void } /** * Extra API methods available to children if they're a descendant of AnimateSharedLayout */ export interface SharedLayoutSyncMethods extends SyncLayoutBatcher { syncUpdate: (force?: boolean) => void forceUpdate: () => void register: (child: VisualElement) => void remove: (child: VisualElement) => void } /** * Default handlers for batching VisualElements */ const defaultHandler: SyncLayoutLifecycles = { measureLayout: (child) => child.updateLayoutMeasurement(), layoutReady: (child) => child.notifyLayoutReady(), } /** * Create a batcher to process VisualElements */ export function createBatcher(): SyncLayoutBatcher { const queue = new Set<VisualElement>() return { add: (child) => queue.add(child), flush: ({ measureLayout, layoutReady, parent } = defaultHandler) => { const order = Array.from(queue).sort((a, b) => a.depth - b.depth) const resetAndMeasure = () => { /** * Write: Reset any transforms on children elements so we can read their actual layout */ order.forEach((child) => child.resetTransform()) /** * Read: Measure the actual layout */ order.forEach(measureLayout) } parent ? parent.withoutTransform(resetAndMeasure) : resetAndMeasure() /** * Write: Notify the VisualElements they're ready for further write operations. */ order.forEach(layoutReady) /** * After all children have started animating, ensure any Entering components are set to Present. * If we add deferred animations (set up all animations and then start them in two loops) this * could be moved to the start loop. But it needs to happen after all the animations configs * are generated in AnimateSharedLayout as this relies on presence data */ order.forEach((child) => { if (child.isPresent) child.presence = Presence.Present }) queue.clear() }, } } export function isSharedLayout( context: SyncLayoutBatcher | SharedLayoutSyncMethods ): context is SharedLayoutSyncMethods { return !!(context as any).forceUpdate } export const SharedLayoutContext = createContext< SyncLayoutBatcher | SharedLayoutSyncMethods >(createBatcher()) /** * @internal */ export const FramerTreeLayoutContext = createContext< SyncLayoutBatcher | SharedLayoutSyncMethods >(createBatcher())