简体   繁体   中英

Animate element along SVG path on scroll in React

There are relatively straightforward ways of accomplishing this in vanilla Javascript (see this for one such approach), but I'm struggling getting something like this to work in React, particularly with an animation library like Framer Motion.

Framer Motion'suseViewPortScroll returns a handy scrollYProgress object whose "current" property tells you what percentage down the page the user is currently scrolled.

I'd like to use this property to do something like this: const pts = path.getPointAtLength(scrollPercentage * pathLength); , and then use pts.x and pts.y for the x and y attributes of, say, a circle SVG - so every time I scroll down (or up) the page, the position of the circle would update/animate along the SVG path.

I'm struggling getting this to work with React's more declarative style, as I'd have to use refs for both the circle and the path elements, meaning I would have to place the aforementioned pts = path.getPointAtLength... code inside of a useEffect call, with both refs as dependencies (or else the refs would be undefined, in which case the pts.x and pts.y properties on x and y of my circle SVG would be inaccessible on first render.

Has anyone solved a similar problem or could possibly provide guidance?

For such a simple animation (dot rotating around the circle) you could make use of simple transform: rotate() :

 const { useState } = React, { render } = ReactDOM, rootNode = document.getElementById('root') const ScrollMeter = ({progress=0}) => ( <svg className="meter" style={{transform:`rotate(${360*progress}deg)`}} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" > <circle cx="50" cy="50" r="40" fill="none" stroke="gray" /> <circle cx="90" cy="50" r="10" fill="gray" stroke="white" /> </svg> ) const App = () => { const [scrollProgress, setScrollProgress] = useState(0), onScroll = ({ target: { scrollTop, scrollHeight } }) => setScrollProgress(scrollTop / scrollHeight) return ( <div> <ScrollMeter progress={scrollProgress} /> <div className="scrollArea" onScroll={onScroll} > { 'there should be some random content here '.repeat(1e3)} </div> </div> ) } render(<App />, rootNode)
 .scrollArea { width: 100%; height: 200px; overflow: auto; }.meter { width: 100px; display: block; position: -webkit-sticky; /* Safari */ position: sticky; top: 0; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

However, if you need something more generic (eg for arbitrary shape of trajectory), you may use the same approach as in the post, you're referring to:

 const { useState, useRef } = React, { render } = ReactDOM, rootNode = document.getElementById('root') const ScrollMeter = ({progress=0}) => { const route = useRef(), routeLength = (route.current && route.current.getTotalLength()), {x,y} = (route.current? route.current.getPointAtLength(routeLength*progress): {x: 90, y:50}) return ( <svg className="meter" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <circle ref={route} cx="50" cy="50" r="40" fill="none" stroke="gray" /> <circle cx={x} cy={y} r="10" fill="gray" stroke="white" /> </svg> ) } const App = () => { const [scrollProgress, setScrollProgress] = useState(0), onScroll = ({ target: { scrollTop, scrollHeight } }) => setScrollProgress(scrollTop / scrollHeight) return ( <div> <ScrollMeter progress={scrollProgress} /> <div className="scrollArea" onScroll={onScroll} > { 'there should be some random content here '.repeat(1e3)} </div> </div> ) } render(<App />, rootNode)
 .scrollArea { width: 100%; height: 200px; overflow: auto; }.meter { width: 100px; display: block; position: -webkit-sticky; /* Safari */ position: sticky; top: 0; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM