1

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

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

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

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

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

4

1 回答 1

2

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

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

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>

于 2020-07-18T23:10:36.137 回答