Skip to content

Commit

Permalink
Merge pull request #1809 from framer/fix/svg-viewbox
Browse files Browse the repository at this point in the history
Fixing SVG viewBox animations
  • Loading branch information
mergetron[bot] committed Dec 5, 2022
2 parents e84b51c + 47d77b8 commit f979393
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 14 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,12 @@ Framer Motion adheres to [Semantic Versioning](http://semver.org/).

Undocumented APIs should be considered internal and may change without warning.

## [7.6.19] 2022-12-05

### Fixed

- Animation of `viewBox` for SVG elements.

## [7.6.18] 2022-12-02

### Changed
Expand Down
14 changes: 7 additions & 7 deletions packages/framer-motion/package.json
Expand Up @@ -72,31 +72,31 @@
"bundlesize": [
{
"path": "./dist/size-rollup-motion.js",
"maxSize": "28.9 kB"
"maxSize": "29 kB"
},
{
"path": "./dist/size-rollup-m.js",
"maxSize": "4.7 kB"
"maxSize": "4.75 kB"
},
{
"path": "./dist/size-rollup-dom-animation.js",
"maxSize": "14.13 kB"
"maxSize": "14.2kB"
},
{
"path": "./dist/size-rollup-dom-max.js",
"maxSize": "24.9 kB"
"maxSize": "24.92 kB"
},
{
"path": "./dist/size-webpack-m.js",
"maxSize": "4.9 kB"
"maxSize": "4.96 kB"
},
{
"path": "./dist/size-webpack-dom-animation.js",
"maxSize": "18.4 kB"
"maxSize": "18.45 kB"
},
{
"path": "./dist/size-webpack-dom-max.js",
"maxSize": "30kB"
"maxSize": "30.05kB"
}
],
"gitHead": "b12ae97e0758be3cca80dcec4869e0e9fc049f63"
Expand Down
34 changes: 34 additions & 0 deletions packages/framer-motion/src/motion/__tests__/component-svg.test.tsx
Expand Up @@ -87,4 +87,38 @@ describe("SVG", () => {
}
render(<Component />)
})

test("doesn't read viewBox as '0 0 0 0'", () => {
const Component = () => {
return (
<motion.svg
viewBox="0 0 100 100"
transition={{ delay: 1 }}
animate={{ viewBox: "100 100 200 200" }}
/>
)
}
const { container } = render(<Component />)
expect(container.firstChild as Element).toHaveAttribute(
"viewBox",
"0 0 100 100"
)
})

test("animates viewBox", () => {
const Component = () => {
return (
<motion.svg
viewBox="0 0 100 100"
transition={{ type: false }}
animate={{ viewBox: "100 100 200 200" }}
/>
)
}
const { container } = render(<Component />)
expect(container.firstChild as Element).toHaveAttribute(
"viewBox",
"100 100 200 200"
)
})
})
7 changes: 6 additions & 1 deletion packages/framer-motion/src/render/dom/use-render.ts
Expand Up @@ -16,7 +16,12 @@ export function createUseRender(forwardMotionProps = false) {
? useSVGProps
: useHTMLProps

const visualProps = useVisualProps(props, latestValues, isStatic)
const visualProps = useVisualProps(
props,
latestValues,
isStatic,
Component
)
const filteredProps = filterProps(
props,
typeof Component === "string",
Expand Down
9 changes: 9 additions & 0 deletions packages/framer-motion/src/render/svg/SVGVisualElement.ts
Expand Up @@ -14,6 +14,7 @@ import { ResolvedValues } from "../types"
import { Box } from "../../projection/geometry/types"
import { createBox } from "../../projection/geometry/models"
import { IProjectionNode } from "../../projection/node/types"
import { isSVGTag } from "./utils/is-svg-tag"

export class SVGVisualElement extends DOMVisualElement<
SVGElement,
Expand All @@ -22,6 +23,8 @@ export class SVGVisualElement extends DOMVisualElement<
> {
type: "svg"

isSVGTag = false

getBaseTargetFromProps(
props: MotionProps,
key: string
Expand Down Expand Up @@ -55,6 +58,7 @@ export class SVGVisualElement extends DOMVisualElement<
renderState,
latestValues,
options,
this.isSVGTag,
props.transformTemplate
)
}
Expand All @@ -67,4 +71,9 @@ export class SVGVisualElement extends DOMVisualElement<
): void {
renderSVG(instance, renderState, styleProp, projection)
}

mount(instance: SVGElement) {
this.isSVGTag = isSVGTag(instance.tagName)
super.mount(instance)
}
}
22 changes: 17 additions & 5 deletions packages/framer-motion/src/render/svg/__tests__/use-props.test.ts
Expand Up @@ -21,7 +21,9 @@ describe("SVG useProps", () => {
cx: 7,
x: 8,
scale: 9,
}
},
false,
"path"
)
)

Expand All @@ -35,9 +37,14 @@ describe("SVG useProps", () => {

test("should return correct styles for element with pathLength", () => {
const { result } = renderHook(() =>
useSVGProps({ style: {} } as any, {
pathLength: 0.5,
})
useSVGProps(
{ style: {} } as any,
{
pathLength: 0.5,
},
false,
"path"
)
)

expect(result.current).toStrictEqual({
Expand All @@ -50,7 +57,12 @@ describe("SVG useProps", () => {

test("should correctly remove props as motionvalues", () => {
const { result } = renderHook(() =>
useSVGProps({ y: motionValue(2) } as any, { attrY: 3 })
useSVGProps(
{ y: motionValue(2) } as any,
{ attrY: 3 },
false,
"path"
)
)

expect(result.current).toStrictEqual({
Expand Down
2 changes: 2 additions & 0 deletions packages/framer-motion/src/render/svg/config-motion.ts
Expand Up @@ -5,6 +5,7 @@ import { scrapeMotionValuesFromProps as scrapeSVGProps } from "./utils/scrape-mo
import { makeUseVisualState } from "../../motion/utils/use-visual-state"
import { createSvgRenderState } from "./utils/create-render-state"
import { buildSVGAttrs } from "./utils/build-attrs"
import { isSVGTag } from "./utils/is-svg-tag"

export const svgMotionConfig: Partial<
MotionComponentConfig<SVGElement, SVGRenderState>
Expand Down Expand Up @@ -33,6 +34,7 @@ export const svgMotionConfig: Partial<
renderState,
latestValues,
{ enableHardwareAcceleration: false },
isSVGTag(instance.tagName),
props.transformTemplate
)

Expand Down
Expand Up @@ -23,6 +23,7 @@ export const lowercaseSVGElements = [
"stop",
"switch",
"symbol",
"svg",
"text",
"tspan",
"use",
Expand Down
9 changes: 8 additions & 1 deletion packages/framer-motion/src/render/svg/use-props.ts
Expand Up @@ -4,15 +4,22 @@ import { copyRawValuesOnly } from "../html/use-props"
import { ResolvedValues } from "../types"
import { buildSVGAttrs } from "./utils/build-attrs"
import { createSvgRenderState } from "./utils/create-render-state"
import { isSVGTag } from "./utils/is-svg-tag"

export function useSVGProps(props: MotionProps, visualState: ResolvedValues) {
export function useSVGProps(
props: MotionProps,
visualState: ResolvedValues,
_isStatic: boolean,
Component: string | React.ComponentType<React.PropsWithChildren<unknown>>
) {
const visualProps = useMemo(() => {
const state = createSvgRenderState()

buildSVGAttrs(
state,
visualState,
{ enableHardwareAcceleration: false },
isSVGTag(Component),
props.transformTemplate
)

Expand Down
12 changes: 12 additions & 0 deletions packages/framer-motion/src/render/svg/utils/build-attrs.ts
Expand Up @@ -23,10 +23,22 @@ export function buildSVGAttrs(
...latest
}: ResolvedValues,
options: DOMVisualElementOptions,
isSVGTag: boolean,
transformTemplate?: MotionProps["transformTemplate"]
) {
buildHTMLStyles(state, latest, options, transformTemplate)

/**
* For svg tags we just want to make sure viewBox is animatable and treat all the styles
* as normal HTML tags.
*/
if (isSVGTag) {
if (state.style.viewBox) {
state.attrs.viewBox = state.style.viewBox
}
return
}

state.attrs = state.style
state.style = {}
const { attrs, style, dimensions } = state
Expand Down
2 changes: 2 additions & 0 deletions packages/framer-motion/src/render/svg/utils/is-svg-tag.ts
@@ -0,0 +1,2 @@
export const isSVGTag = (tag: unknown) =>
typeof tag === "string" && tag.toLowerCase() === "svg"

0 comments on commit f979393

Please sign in to comment.