Skip to content

Commit

Permalink
Removing early hydration of nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
mattgperry committed May 3, 2023
1 parent cceedcb commit 60c683a
Show file tree
Hide file tree
Showing 13 changed files with 14 additions and 110 deletions.
8 changes: 4 additions & 4 deletions dev/projection/new-element-concurrent.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
const boxOrigin = box.getBoundingClientRect()
const a = document.createElement("div")
a.id = "child"
a.setAttribute("data-projection-id", "a")

// Render phase
const aProjection = createNode(
Expand All @@ -65,7 +64,7 @@
{ layout: true },
"a"
)
const bProjection = new HTMLProjectionNode("b", {}, boxProjection)
const bProjection = new HTMLProjectionNode({}, boxProjection)

// Snapshot
boxProjection.willUpdate()
Expand All @@ -77,9 +76,10 @@
// First layout effect
boxProjection.root.didUpdate()

// A/B mounts
aProjection.mount(a)

frame.postRender(() => {
// A/B mounts
aProjection.mount(a)
matchViewportBox(box, boxOrigin)
matchViewportBox(a, {
bottom: 70,
Expand Down
4 changes: 1 addition & 3 deletions dev/projection/script-animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ addScaleCorrector({
boxShadow: correctBoxShadow,
})

let id = 1
Animate.createNode = (
element,
parent,
Expand All @@ -54,8 +53,7 @@ Animate.createNode = (
visualElement.scheduleRender()
}

id++
const node = new HTMLProjectionNode(id, latestValues, parent)
const node = new HTMLProjectionNode(latestValues, parent)

node.setOptions({
scheduleRender: scheduleRender,
Expand Down
4 changes: 1 addition & 3 deletions dev/projection/script-undo.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ addScaleCorrector({
boxShadow: correctBoxShadow,
})

let id = 1
Undo.createNode = (element, parent, options = {}, overrideId) => {
const latestValues = {}
const visualElement = new HTMLVisualElement({
Expand All @@ -49,8 +48,7 @@ Undo.createNode = (element, parent, options = {}, overrideId) => {
visualElement.scheduleRender()
}

id++
const node = new HTMLProjectionNode(overrideId || id, latestValues, parent)
const node = new HTMLProjectionNode(latestValues, parent)

node.setOptions({
animate: false,
Expand Down
1 change: 0 additions & 1 deletion packages/framer-motion-3d/src/render/use-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export const useRender: RenderComponent<
> = (
Component,
props: ThreeMotionProps & MeshProps,
_projectionId,
ref,
_state,
isStatic,
Expand Down
2 changes: 1 addition & 1 deletion packages/framer-motion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,5 @@
"maxSize": "31.14 kB"
}
],
"gitHead": "23a64f5f7dec6bc2d2df3f7ea8a661b91ae239d7"
"gitHead": "936e9ee810d2c720d273ab2d919526c6a096e730"
}
5 changes: 2 additions & 3 deletions packages/framer-motion/src/motion/features/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import type { Feature } from "./Feature"
import { MeasureLayout } from "./layout/MeasureLayout"

interface FeatureClass<Props = unknown> {
new (props: Props): Feature<Props>;
new (props: Props): Feature<Props>
}

export type HydratedFeatureDefinition = {
isEnabled: (props: MotionProps) => boolean
Feature: FeatureClass<unknown>;
Feature: FeatureClass<unknown>
ProjectionNode?: any
MeasureLayout?: typeof MeasureLayout
}
Expand Down Expand Up @@ -59,7 +59,6 @@ export type LazyFeatureBundle = () => Promise<FeatureBundle>
export type RenderComponent<Instance, RenderState> = (
Component: string | React.ComponentType<React.PropsWithChildren<unknown>>,
props: MotionProps,
projectionId: number | undefined,
ref: React.Ref<Instance>,
visualState: VisualState<Instance, RenderState>,
isStatic: boolean,
Expand Down
16 changes: 0 additions & 16 deletions packages/framer-motion/src/motion/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useMotionRef } from "./utils/use-motion-ref"
import { useCreateMotionContext } from "../context/MotionContext/create"
import { loadFeatures } from "./features/load-features"
import { isBrowser } from "../utils/is-browser"
import { useProjectionId } from "../projection/node/id"
import { LayoutGroupContext } from "../context/LayoutGroupContext"
import { LazyContext } from "../context/LazyContext"
import { SwitchLayoutGroupContext } from "../context/SwitchLayoutGroupContext"
Expand Down Expand Up @@ -63,19 +62,6 @@ export function createMotionComponent<Props extends {}, Instance, RenderState>({

const context = useCreateMotionContext<Instance>(props)

/**
* Create a unique projection ID for this component. If a new component is added
* during a layout animation we'll use this to query the DOM and hydrate its ref early, allowing
* us to measure it as soon as any layout effect flushes pending layout animations.
*
* Performance note: It'd be better not to have to search the DOM for these elements.
* For newly-entering components it could be enough to only correct treeScale, in which
* case we could mount in a scale-correction mode. This wouldn't be enough for
* shared element transitions however. Perhaps for those we could revert to a root node
* that gets forceRendered and layout animations are triggered on its layout effect.
*/
const projectionId = isStatic ? undefined : useProjectionId()

const visualState = useVisualState(props, isStatic)

if (!isStatic && isBrowser) {
Expand Down Expand Up @@ -107,7 +93,6 @@ export function createMotionComponent<Props extends {}, Instance, RenderState>({
configAndProps,
isStrict,
preloadedFeatures,
projectionId,
initialLayoutGroupConfig
)
}
Expand All @@ -128,7 +113,6 @@ export function createMotionComponent<Props extends {}, Instance, RenderState>({
{useRender(
Component,
props,
projectionId,
useMotionRef<Instance, RenderState>(
visualState,
context.visualElement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const HTMLProjectionNode = createProjectionNode<HTMLElement>({
}),
defaultParent: () => {
if (!rootProjectionNode.current) {
const documentNode = new DocumentProjectionNode(0, {})
const documentNode = new DocumentProjectionNode({})
documentNode.mount(window)
documentNode.setOptions({ layoutScroll: true })
rootProjectionNode.current = documentNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,6 @@ export function createProjectionNode<I>({
*/
id: number = id++

/**
* A unique ID generated for every projection element beyond the initial render.
*
* The projection tree's `didUpdate` function will be triggered by the first element
* in the tree to run its layout effects. However, if there are elements entering the tree
* these might not be mounted yet. When React renders a `motion` component we
* give it a unique selector and register it as a potential projection node (not all
* rendered components will be committed by React). In `didUpdate`, we search the DOM for
* these potential nodes with this id and hydrate the projetion node of the ones that were commited.
*/
elementId: number | undefined

/**
* An id that represents a unique session instigated by startUpdate.
*/
Expand Down Expand Up @@ -335,20 +323,16 @@ export function createProjectionNode<I>({
preserveOpacity?: boolean

constructor(
elementId: number | undefined,
latestValues: ResolvedValues = {},
parent: IProjectionNode | undefined = defaultParent?.()
) {
this.elementId = elementId
this.latestValues = latestValues
this.root = parent ? parent.root || parent : this
this.path = parent ? [...parent.path, parent] : []
this.parent = parent

this.depth = parent ? parent.depth + 1 : 0

elementId && this.root.registerPotentialNode(elementId, this)

for (let i = 0; i < this.path.length; i++) {
this.path[i].shouldResetTransform = true
}
Expand All @@ -373,18 +357,12 @@ export function createProjectionNode<I>({
return this.eventHandlers.has(name)
}

// Note: Currently only running on root node
potentialNodes = new Map<number, IProjectionNode>()
registerPotentialNode(elementId: number, node: IProjectionNode) {
this.potentialNodes.set(elementId, node)
}

/**
* Lifecycles
*/
mount(instance: I, isLayoutDirty = false) {
if (this.instance) return

console.log("mount", instance)
this.isSVG = isSVGElement(instance)

this.instance = instance
Expand All @@ -396,7 +374,6 @@ export function createProjectionNode<I>({

this.root.nodes!.add(this)
this.parent && this.parent.children.add(this)
this.elementId && this.root.potentialNodes.delete(this.elementId)

if (isLayoutDirty && (layout || layoutId)) {
this.isLayoutDirty = true
Expand Down Expand Up @@ -613,6 +590,7 @@ export function createProjectionNode<I>({
updateScheduled = false

update() {
console.log("process frame")
this.updateScheduled = false

const updateWasBlocked = this.isUpdateBlocked()
Expand All @@ -631,17 +609,6 @@ export function createProjectionNode<I>({

this.isUpdating = false

/**
* Search for and mount newly-added projection elements.
*
* TODO: Every time a new component is rendered we could search up the tree for
* the closest mounted node and query from there rather than document.
*/
if (this.potentialNodes.size) {
this.potentialNodes.forEach(mountNodeEarly)
this.potentialNodes.clear()
}

/**
* Write
*/
Expand Down Expand Up @@ -2077,27 +2044,6 @@ const defaultLayoutTransition = {
ease: [0.4, 0, 0.1, 1],
}

function mountNodeEarly(node: IProjectionNode, elementId: number) {
/**
* Rather than searching the DOM from document we can search the
* path for the deepest mounted ancestor and search from there
*/
let searchNode = node.root
for (let i = node.path.length - 1; i >= 0; i--) {
if (Boolean(node.path[i].instance)) {
searchNode = node.path[i]
break
}
}
const searchElement =
searchNode && searchNode !== node.root ? searchNode.instance : document

const element = (searchElement as Element).querySelector(
`[data-projection-id="${elementId}"]`
)
if (element) node.mount(element, true)
}

function roundAxis(axis: Axis): void {
axis.min = Math.round(axis.min)
axis.max = Math.round(axis.max)
Expand Down
11 changes: 0 additions & 11 deletions packages/framer-motion/src/projection/node/id.ts

This file was deleted.

3 changes: 0 additions & 3 deletions packages/framer-motion/src/projection/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export type LayoutEvents =

export interface IProjectionNode<I = unknown> {
id: number
elementId: number | undefined
animationId: number
parent?: IProjectionNode
relativeParent?: IProjectionNode
Expand Down Expand Up @@ -88,9 +87,7 @@ export interface IProjectionNode<I = unknown> {
scheduleUpdateProjection(): void
scheduleCheckAfterUnmount(): void
checkUpdateFailed(): void
potentialNodes: Map<number, IProjectionNode>
sharedNodes: Map<string, NodeStack>
registerPotentialNode(id: number, node: IProjectionNode): void
registerSharedNode(id: string, node: IProjectionNode): void
getStack(): NodeStack | undefined
isVisible: boolean
Expand Down
4 changes: 1 addition & 3 deletions packages/framer-motion/src/render/VisualElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export abstract class VisualElement<

visualElementStore.set(instance, this)

if (this.projection) {
if (this.projection && !this.projection.instance) {
this.projection.mount(instance)
}

Expand Down Expand Up @@ -493,7 +493,6 @@ export abstract class VisualElement<
{ children, ...renderedProps }: MotionProps,
isStrict: boolean,
preloadedFeatures?: FeatureBundle,
projectionId?: number,
initialLayoutGroupConfig?: SwitchLayoutGroupContext
) {
let ProjectionNodeConstructor: any
Expand Down Expand Up @@ -538,7 +537,6 @@ export abstract class VisualElement<

if (!this.projection && ProjectionNodeConstructor) {
this.projection = new ProjectionNodeConstructor(
projectionId,
this.latestValues,
this.parent && this.parent.projection
) as IProjectionNode
Expand Down
6 changes: 1 addition & 5 deletions packages/framer-motion/src/render/dom/use-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function createUseRender(forwardMotionProps = false) {
const useRender: RenderComponent<
HTMLElement | SVGElement,
HTMLRenderState | SVGRenderState
> = (Component, props, projectionId, ref, { latestValues }, isStatic) => {
> = (Component, props, ref, { latestValues }, isStatic) => {
const useVisualProps = isSVGComponent(Component)
? useSVGProps
: useHTMLProps
Expand Down Expand Up @@ -45,10 +45,6 @@ export function createUseRender(forwardMotionProps = false) {
[children]
)

if (projectionId) {
elementProps["data-projection-id"] = projectionId
}

return createElement<any>(Component, {
...elementProps,
children: renderedChildren,
Expand Down

0 comments on commit 60c683a

Please sign in to comment.