Skip to content

Commit

Permalink
Merge pull request #1026 from framer/fix/canvas-memory-usage
Browse files Browse the repository at this point in the history
Reducing canvas memory usage
  • Loading branch information
mergetron[bot] committed Mar 12, 2021
2 parents ed7ea87 + ab68333 commit def9f7f
Show file tree
Hide file tree
Showing 88 changed files with 1,734 additions and 1,072 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"react/jsx-pascal-case": "error",
"react-hooks/rules-of-hooks": "error",
// "react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"import/no-default-export": "error",
"prefer-arrow-callback": "warn",
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Framer Motion adheres to [Semantic Versioning](http://semver.org/).

## [3.10.3] 2021-03-10

### Fixed

- Reduced memory consumption in static mode by not loading `VisualElement`.

## [3.10.2] 2021-03-09

### Fixed
Expand Down
29 changes: 15 additions & 14 deletions api/framer-motion.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { PropsWithoutRef } from 'react';
import * as PropTypes from 'prop-types';
import * as React from 'react';
import { ReactHTML } from 'react';
import { Ref } from 'react';
import { RefAttributes } from 'react';
import { RefObject } from 'react';
import { SpringOptions } from 'popmotion';
Expand Down Expand Up @@ -53,8 +52,10 @@ export class AnimateSharedLayout extends React.Component<SharedLayoutProps, {},
componentDidMount(): void;
// (undocumented)
componentDidUpdate(): void;
// Warning: (ae-forgotten-export) The symbol "MotionContextProps" needs to be exported by the entry point index.d.ts
//
// (undocumented)
static contextType: React.Context<VisualElement<any, any> | undefined>;
static contextType: React.Context<MotionContextProps>;
// (undocumented)
render(): JSX.Element;
// (undocumented)
Expand Down Expand Up @@ -183,16 +184,16 @@ export function createBatcher(): SyncLayoutBatcher;
// @public (undocumented)
export function createCrossfader(): Crossfader;

// Warning: (ae-forgotten-export) The symbol "MotionComponents" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "DOMMotionComponents" needs to be exported by the entry point index.d.ts
//
// @public
export function createDomMotionComponent<T extends keyof MotionComponents>(key: T): MotionComponents[T];
export function createDomMotionComponent<T extends keyof DOMMotionComponents>(key: T): DOMMotionComponents[T];

// Warning: (ae-forgotten-export) The symbol "MotionComponentConfig" needs to be exported by the entry point index.d.ts
// Warning: (ae-internal-missing-underscore) The name "createMotionComponent" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal
export function createMotionComponent<P extends {}, E>({ defaultFeatures, createVisualElement, useRender, }: MotionComponentConfig<E>): React.ForwardRefExoticComponent<React.PropsWithoutRef<P & MotionProps> & React.RefAttributes<E>>;
export function createMotionComponent<Props extends {}, Instance, RenderState>({ defaultFeatures, createVisualElement, useRender, useVisualState, }: MotionComponentConfig<Instance, RenderState>): React.ForwardRefExoticComponent<React.PropsWithoutRef<Props & MotionProps> & React.RefAttributes<Instance>>;

// Warning: (ae-forgotten-export) The symbol "React" needs to be exported by the entry point index.d.ts
// Warning: (ae-internal-missing-underscore) The name "CustomDomComponent" should be prefixed with an underscore because the declaration is marked as @internal
Expand Down Expand Up @@ -370,12 +371,12 @@ export interface LayoutProps {
}

// @public (undocumented)
export const m: (<Props>(Component: string | import("react").ComponentClass<Props, any> | import("react").FunctionComponent<Props>, { forwardMotionProps }?: import("./motion-proxy").DomMotionComponentConfig) => import("./motion-proxy").CustomDomComponent<Props>) & import("./types").HTMLMotionComponents & import("./types").SVGMotionComponents & {
export const m: (<Props>(Component: string | import("react").ComponentClass<Props, any> | import("react").FunctionComponent<Props>, { forwardMotionProps }?: import("./motion-proxy").DomMotionComponentConfig) => import("./motion-proxy").CustomDomComponent<Props>) & import("../html/types").HTMLMotionComponents & import("../svg/types").SVGMotionComponents & {
custom: <Props>(Component: string | import("react").ComponentClass<Props, any> | import("react").FunctionComponent<Props>, { forwardMotionProps }?: import("./motion-proxy").DomMotionComponentConfig) => import("./motion-proxy").CustomDomComponent<Props>;
};

// @public
export const motion: (<Props>(Component: string | import("react").ComponentClass<Props, any> | import("react").FunctionComponent<Props>, { forwardMotionProps }?: import("./motion-proxy").DomMotionComponentConfig) => import("./motion-proxy").CustomDomComponent<Props>) & import("./types").HTMLMotionComponents & import("./types").SVGMotionComponents & {
export const motion: (<Props>(Component: string | import("react").ComponentClass<Props, any> | import("react").FunctionComponent<Props>, { forwardMotionProps }?: import("./motion-proxy").DomMotionComponentConfig) => import("./motion-proxy").CustomDomComponent<Props>) & import("../html/types").HTMLMotionComponents & import("../svg/types").SVGMotionComponents & {
custom: <Props>(Component: string | import("react").ComponentClass<Props, any> | import("react").FunctionComponent<Props>, { forwardMotionProps }?: import("./motion-proxy").DomMotionComponentConfig) => import("./motion-proxy").CustomDomComponent<Props>;
};

Expand Down Expand Up @@ -834,7 +835,7 @@ export enum VisibilityAction {
// Warning: (ae-forgotten-export) The symbol "LifecycleManager" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export interface VisualElement<Instance = any, MutableState = any> extends LifecycleManager {
export interface VisualElement<Instance = any, RenderState = any> extends LifecycleManager {
// (undocumented)
addChild(child: VisualElement): () => void;
// (undocumented)
Expand All @@ -852,9 +853,7 @@ export interface VisualElement<Instance = any, MutableState = any> extends Lifec
// (undocumented)
blockInitialAnimation?: boolean;
// (undocumented)
build(): MutableState;
// (undocumented)
clearState(props: MotionProps): void;
build(): RenderState;
// (undocumented)
current: Instance | null;
// (undocumented)
Expand Down Expand Up @@ -928,6 +927,8 @@ export interface VisualElement<Instance = any, MutableState = any> extends Lifec
// (undocumented)
measureViewportBox(withTransform?: boolean): AxisBox2D;
// (undocumented)
mount(instance: Instance): void;
// (undocumented)
notifyLayoutReady(config?: SharedLayoutAnimationConfig): void;
// (undocumented)
path: VisualElement[];
Expand All @@ -952,8 +953,6 @@ export interface VisualElement<Instance = any, MutableState = any> extends Lifec
// (undocumented)
rebaseProjectionTarget(force?: boolean, sourceBox?: AxisBox2D): void;
// (undocumented)
ref: Ref<Instance | null>;
// (undocumented)
removeValue(key: string): void;
// (undocumented)
resetTransform(): void;
Expand Down Expand Up @@ -990,6 +989,8 @@ export interface VisualElement<Instance = any, MutableState = any> extends Lifec
// (undocumented)
unlockProjectionTarget(): void;
// (undocumented)
unmount(): void;
// (undocumented)
updateLayoutMeasurement(): void;
// (undocumented)
updateLayoutProjection(): void;
Expand All @@ -1003,7 +1004,7 @@ export interface VisualElement<Instance = any, MutableState = any> extends Lifec
// Warning: (ae-forgotten-export) The symbol "VisualElementOptions" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export const visualElement: <Instance, MutableState, Options>({ treeType, createRenderState, build, getBaseTarget, makeTargetAnimatable, measureViewportBox, onMount, render: renderInstance, readValueFromInstance, resetTransform, restoreTransform, removeValueFromMutableState, sortNodePosition, scrapeMotionValuesFromProps, }: VisualElementConfig<Instance, MutableState, Options>) => ({ parent, ref: externalRef, props, isStatic, presenceId, blockInitialAnimation, }: VisualElementOptions<Instance>, options?: Options) => VisualElement<Instance, any>;
export const visualElement: <Instance, MutableState, Options>({ treeType, build, getBaseTarget, makeTargetAnimatable, measureViewportBox, render: renderInstance, readValueFromInstance, resetTransform, restoreTransform, removeValueFromRenderState, sortNodePosition, scrapeMotionValuesFromProps, }: VisualElementConfig<Instance, MutableState, Options>) => ({ parent, props, presenceId, blockInitialAnimation, visualState, }: VisualElementOptions<Instance, any>, options?: Options) => VisualElement<Instance, any>;

// @public
export interface VisualElementLifecycles {
Expand Down
46 changes: 46 additions & 0 deletions cypress/integration/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,50 @@ describe("SVG", () => {
expect(bottom).to.equal(450)
})
})
it("Correctly applies transforms in static mode", () => {
cy.visit("?test=svg&isStatic=true")
.wait(200)
.get("[data-testid='rotate']")
.should(($rotate: any) => {
const rotate = $rotate[0] as SVGRectElement
const {
top,
left,
right,
bottom,
} = rotate.getBoundingClientRect()
expect(Math.round(top)).to.equal(29)
expect(Math.round(left)).to.equal(29)
expect(Math.round(right)).to.equal(171)
expect(Math.round(bottom)).to.equal(171)
})
.get("[data-testid='scale']")
.should(($scale: any) => {
const scale = $scale[0] as SVGRectElement
const {
top,
left,
right,
bottom,
} = scale.getBoundingClientRect()
expect(top).to.equal(150)
expect(left).to.equal(0)
expect(right).to.equal(200)
expect(bottom).to.equal(350)
})
.get("[data-testid='translate']")
.should(($translate: any) => {
const translate = $translate[0] as SVGRectElement
const {
top,
left,
right,
bottom,
} = translate.getBoundingClientRect()
expect(top).to.equal(350)
expect(left).to.equal(150)
expect(right).to.equal(250)
expect(bottom).to.equal(450)
})
})
})
68 changes: 36 additions & 32 deletions dev/tests/svg.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
import * as React from "react"
import { motion, motionValue } from "@framer"
import { motion, motionValue, MotionConfig } from "@framer"

/**
* An example of providing a MotionValue to an SVG component via its props
*/

export const App = () => {
const params = new URLSearchParams(window.location.search)
const isStatic = Boolean(params.get("isStatic"))
return (
<svg
width="1000"
height="1000"
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
>
<motion.rect
height="100"
width="100"
x={motionValue(50)}
y={50}
data-testid="rotate"
style={{ rotate: 45 }}
/>
<motion.rect
height="100"
width="100"
x={50}
y={200}
data-testid="scale"
style={{ scale: 2 }}
/>
<motion.rect
height="100"
width="100"
x={50}
y={350}
data-testid="translate"
style={{ x: 100 }}
/>
</svg>
<MotionConfig isStatic={isStatic}>
<svg
width="1000"
height="1000"
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
>
<motion.rect
height="100"
width="100"
x={motionValue(50)}
y={50}
data-testid="rotate"
style={{ rotate: 45 }}
/>
<motion.rect
height="100"
width="100"
x={50}
y={200}
data-testid="scale"
style={{ scale: 2 }}
/>
<motion.rect
height="100"
width="100"
x={50}
y={350}
data-testid="translate"
style={{ x: 100 }}
/>
</svg>
</MotionConfig>
)
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "framer-motion",
"version": "3.10.2",
"version": "3.10.3-rc.2",
"description": "A simple and powerful React animation library",
"main": "dist/framer-motion.cjs.js",
"module": "dist/es/index.js",
Expand Down Expand Up @@ -50,6 +50,7 @@
"@rollup/plugin-replace": "^2.3.2",
"@testing-library/dom": "^6.10.1",
"@testing-library/react": "^9.3.2",
"@testing-library/react-hooks": "^5.1.0",
"@types/jest": "^23.3.9",
"@types/node": "12.7.4",
"@types/react": "^16.9.11",
Expand Down Expand Up @@ -124,11 +125,11 @@
"bundlesize": [
{
"path": "./dist/framer-motion.js",
"maxSize": "29.4 kB"
"maxSize": "30 kB"
},
{
"path": "./dist/minimal-component.js",
"maxSize": "13.5 kB"
"maxSize": "14 kB"
}
]
}
24 changes: 16 additions & 8 deletions src/animation/use-animated-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ import { visualElement } from "../render"
import { ResolvedValues } from "../render/types"
import { axisBox } from "../utils/geometry"
import { animateVisualElement } from "../render/utils/animation"
import { makeUseVisualState } from "../motion/utils/use-visual-state"

interface AnimatedStateOptions {
initialState: ResolvedValues
}

const createObject = () => ({})

const stateVisualElement = visualElement<
ResolvedValues,
{},
AnimatedStateOptions
>({
createRenderState: () => ({}),
build() {},
measureViewportBox: axisBox,
resetTransform() {},
restoreTransform() {},
removeValueFromMutableState() {},
removeValueFromRenderState() {},
render() {},
scrapeMotionValuesFromProps() {
return {}
},
scrapeMotionValuesFromProps: createObject,

readValueFromInstance(_state, key, options) {
return options.initialState[key] || 0
Expand All @@ -38,20 +38,28 @@ const stateVisualElement = visualElement<
},
})

const useVisualState = makeUseVisualState({
scrapeMotionValuesFromProps: createObject,
createRenderState: createObject,
})

/**
* This is not an officially supported API and may be removed
* on any version.
* @internal
*/
export function useAnimatedState(initialState: any) {
const [animationState, setAnimationState] = useState(initialState)

const visualState = useVisualState({}, false)

const element = useConstant(() =>
stateVisualElement({ props: {} }, { initialState })
stateVisualElement({ props: {}, visualState }, { initialState })
)

useEffect(() => {
;(element.ref as any)({})
return () => (element.ref as any)(null)
element.mount({})
return element.unmount()
}, [])

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/AnimatePresence/PresenceChild.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react"
import { useMemo } from "react"
import { PresenceContext } from "./PresenceContext"
import { PresenceContext } from "../../context/PresenceContext"
import { VariantLabels } from "../../motion/types"
import { useConstant } from "../../utils/use-constant"

Expand Down
2 changes: 1 addition & 1 deletion src/components/AnimatePresence/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { PresenceChild } from "./PresenceChild"
import {
SharedLayoutContext,
isSharedLayout,
} from "../AnimateSharedLayout/SharedLayoutContext"
} from "../../context/SharedLayoutContext"

type ComponentKey = string | number

Expand Down
5 changes: 4 additions & 1 deletion src/components/AnimatePresence/use-presence.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { useContext, useEffect } from "react"
import { PresenceContext, PresenceContextProps } from "./PresenceContext"
import {
PresenceContext,
PresenceContextProps,
} from "../../context/PresenceContext"
import { useConstant } from "../../utils/use-constant"

export type SafeToRemove = () => void
Expand Down
Loading

0 comments on commit def9f7f

Please sign in to comment.