简体   繁体   English

在 React 中滚动时沿 SVG 路径为元素设置动画

[英]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.在 vanilla Javascript 中有相对简单的方法来实现这一点(请参阅this for a这样的方法),但我正在努力让这样的东西在 React 中工作,特别是使用像 Framer Motion 这样的 animation 库。

Framer Motion'suseViewPortScroll returns a handy scrollYProgress object whose "current" property tells you what percentage down the page the user is currently scrolled. Framer Motion 的useViewPortScroll返回一个方便的 scrollYProgress object ,其“当前”属性告诉您用户当前向下滚动页面的百分比。

I'd like to use this property to do something like this: const pts = path.getPointAtLength(scrollPercentage * pathLength);我想用这个属性来做这样的事情: 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. ,然后使用pts.xpts.y作为圆 SVG 的 x 和 y 属性 - 所以每次我向下(或向上)滚动页面时,圆的 position 将沿着 ZCD15A75C260F089436B 更新/动画小路。

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.我正在努力让它与 React 更具声明性的风格一起使用,因为我必须对 circle 和 path 元素使用 refs,这意味着我必须将前面提到的pts = path.getPointAtLength...代码放在里面一个 useEffect 调用,两个 refs 作为依赖项(否则 refs 将是未定义的,在这种情况下,我的圈子 SVG 的 x 和 y 上的pts.xpts.y属性在第一次渲染时将无法访问。

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() :对于这样一个简单的 animation (点围绕圆圈旋转),您可以使用简单的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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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