【问题标题】:Animate element along SVG path on scroll in React在 React 中滚动时沿 SVG 路径为元素设置动画
【发布时间】:2020-07-18 17:34:28
【问题描述】:

在 vanilla Javascript 中有相对简单的方法可以实现这一点(请参阅 this 了解其中一种方法),但我正在努力让类似的东西在 React 中工作,尤其是使用像 Framer Motion 这样的动画库。

Framer Motion 的 useViewPortScroll 返回一个方便的 scrollYProgress 对象,其“current”属性告诉您用户当前滚动页面的百分比。

我想使用这个属性来做这样的事情:const pts = path.getPointAtLength(scrollPercentage * pathLength);,然后使用pts.xpts.y 作为圆形 SVG 的 x 和 y 属性 - 所以每次我滚动向下(或向上)页面,圆圈的位置将沿 SVG 路径更新/动画。

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

有没有人解决过类似的问题或者可以提供指导?

【问题讨论】:

    标签: javascript reactjs svg framer-motion


    【解决方案1】:

    对于这样一个简单的动画(圆点旋转),你可以使用简单的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;
    }
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"&gt;&lt;/script&gt;&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"&gt;&lt;/script&gt;&lt;div id="root"&gt;&lt;/div&gt;

    但是,如果您需要更通用的东西(例如,对于任意形状的轨迹),您可以使用与帖子中相同的方法,您指的是:

    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;
    }
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"&gt;&lt;/script&gt;&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"&gt;&lt;/script&gt;&lt;div id="root"&gt;&lt;/div&gt;

    【讨论】:

    • 谢谢!不过,我很难理解如何让它在没有固定高度和 scrollArea 元素上自动溢出的情况下工作。我希望在用户滚动主体时发生动画,而不是带有自己专用滚动条的内部 div。
    • @user11424535 :上面的代码演示了将scroll事件附加到SVG元素属性的方式。您刚刚描述的问题似乎有所不同,并且与捕获 proper 滚动事件(对于整个文档)有关。如果您在某个时候卡住了,您可能会发布一个单独的问题来描述您的问题 - cmets 根本不适合解决此类问题。
    猜你喜欢
    • 2012-07-14
    • 2021-05-16
    • 1970-01-01
    • 1970-01-01
    • 2013-03-18
    • 2019-04-10
    • 2020-01-29
    • 2021-06-29
    • 2021-07-20
    相关资源
    最近更新 更多