From d26a7aa564303648a98464ba552f54aea920c935 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Wed, 30 Aug 2023 12:13:16 +0200 Subject: [PATCH] Fixing SVG as target --- dev/tests/scroll-svg.tsx | 47 +++++++++++++++++ .../cypress/integration/scroll.ts | 40 +++++++++++++++ .../src/render/dom/scroll/offsets/index.ts | 8 ++- .../src/render/dom/scroll/offsets/inset.ts | 50 ++++++++++++------- 4 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 dev/tests/scroll-svg.tsx diff --git a/dev/tests/scroll-svg.tsx b/dev/tests/scroll-svg.tsx new file mode 100644 index 0000000000..ac20dd2756 --- /dev/null +++ b/dev/tests/scroll-svg.tsx @@ -0,0 +1,47 @@ +import * as React from "react" +import { useRef } from "react" +import { motion, useScroll } from "framer-motion" + +export const App = () => { + const rect = useRef(null) + const svg = useRef(null) + + const rectValues = useScroll({ + target: rect, + offset: ["start end", "end start"], + }) + + const svgValues = useScroll({ + target: svg, + offset: ["start end", "end start"], + }) + + return ( + <> +
+ + + +
+ + {rectValues.scrollYProgress} + + + {svgValues.scrollYProgress} + + + ) +} + +const fixed: React.CSSProperties = { + position: "fixed", + top: 10, + left: 10, +} diff --git a/packages/framer-motion/cypress/integration/scroll.ts b/packages/framer-motion/cypress/integration/scroll.ts index 8fe1133b57..05d0ebc195 100644 --- a/packages/framer-motion/cypress/integration/scroll.ts +++ b/packages/framer-motion/cypress/integration/scroll.ts @@ -114,3 +114,43 @@ describe("scroll() animation", () => { }) }) }) + +describe("SVG", () => { + it("tracks SVG elements as target", () => { + cy.visit("?test=scroll-svg").wait(100).viewport(100, 400) + cy.get("#rect-progress").should(([$element]: any) => { + expect($element.innerText).to.be("0") + }) + cy.get("#svg-progress").should(([$element]: any) => { + expect($element.innerText).to.be("0") + }) + cy.scrollTo(0, 25) + cy.get("#rect-progress").should(([$element]: any) => { + expect($element.innerText).not.to.be("0") + }) + cy.get("#svg-progress").should(([$element]: any) => { + expect($element.innerText).to.be("0") + }) + cy.scrollTo(0, 75) + cy.get("#rect-progress").should(([$element]: any) => { + expect($element.innerText).not.to.be("0") + }) + cy.get("#svg-progress").should(([$element]: any) => { + expect($element.innerText).not.to.be("0") + }) + cy.scrollTo(0, 500) + cy.get("#rect-progress").should(([$element]: any) => { + expect($element.innerText).not.to.be("1") + }) + cy.get("#svg-progress").should(([$element]: any) => { + expect($element.innerText).not.to.be("1") + }) + cy.scrollTo(0, 600) + cy.get("#rect-progress").should(([$element]: any) => { + expect($element.innerText).to.be("1") + }) + cy.get("#svg-progress").should(([$element]: any) => { + expect($element.innerText).to.be("1") + }) + }) +}) diff --git a/packages/framer-motion/src/render/dom/scroll/offsets/index.ts b/packages/framer-motion/src/render/dom/scroll/offsets/index.ts index ca5d6a831a..8d25efb853 100644 --- a/packages/framer-motion/src/render/dom/scroll/offsets/index.ts +++ b/packages/framer-motion/src/render/dom/scroll/offsets/index.ts @@ -8,6 +8,12 @@ import { defaultOffset } from "../../../../utils/offsets/default" const point = { x: 0, y: 0 } +function getTargetSize(target: Element) { + return "getBBox" in target && target.tagName !== "svg" + ? (target as SVGGraphicsElement).getBBox() + : { width: target.clientWidth, height: target.clientHeight } +} + export function resolveOffsets( container: HTMLElement, info: ScrollInfo, @@ -27,7 +33,7 @@ export function resolveOffsets( const targetSize = target === container ? { width: container.scrollWidth, height: container.scrollHeight } - : { width: target.clientWidth, height: target.clientHeight } + : getTargetSize(target) const containerSize = { width: container.clientWidth, diff --git a/packages/framer-motion/src/render/dom/scroll/offsets/inset.ts b/packages/framer-motion/src/render/dom/scroll/offsets/inset.ts index 41e83e59e8..a1345a363e 100644 --- a/packages/framer-motion/src/render/dom/scroll/offsets/inset.ts +++ b/packages/framer-motion/src/render/dom/scroll/offsets/inset.ts @@ -1,25 +1,37 @@ export function calcInset(element: Element, container: HTMLElement) { - let inset = { x: 0, y: 0 } + const inset = { x: 0, y: 0 } - let current: Element | null = element - while (current && current !== container) { - if (current instanceof HTMLElement) { - inset.x += current.offsetLeft - inset.y += current.offsetTop - current = current.offsetParent - } else if (current instanceof SVGGraphicsElement && "getBBox" in current) { - const { top, left } = current.getBBox() - inset.x += left - inset.y += top + let current: Element | null = element + while (current && current !== container) { + if (current instanceof HTMLElement) { + inset.x += current.offsetLeft + inset.y += current.offsetTop + current = current.offsetParent + } else if (current.tagName === "svg") { + const svgBoundingBox = current.getBoundingClientRect() + current = current.parentElement! + const parentBoundingBox = current.getBoundingClientRect() + inset.x += svgBoundingBox.left - parentBoundingBox.left + inset.y += svgBoundingBox.top - parentBoundingBox.top + } else if ( + current instanceof SVGGraphicsElement && + "getBBox" in current + ) { + const { x, y } = current.getBBox() + inset.x += x + inset.y += y - /** - * Assign the next parent element as the tag. - */ - while (current && current.tagName !== "svg") { - current = current.parentNode as SVGElement - } + let svg: SVGElement | null = null + let parent: SVGElement = current.parentNode as SVGElement + while (!svg) { + if (parent.tagName === "svg") { + svg = parent + } + parent = current.parentNode as SVGElement + } + current = svg + } } - } - return inset + return inset }