在 React 中滚动时沿 SVG 路径设置动画元素
Animate element along SVG path on scroll in React
在 vanilla Javascript 中有相对简单的方法来完成此操作(请参阅 了解其中一种方法),但我正在努力让类似的东西在 React 中工作,尤其是使用像 Framer Motion 这样的动画库。
Framer Motion 的 useViewPortScroll returns 一个方便的 scrollYProgress 对象,其“当前”属性 告诉您用户当前向下滚动页面的百分比。
我想用这个 属性 做这样的事情:const pts = path.getPointAtLength(scrollPercentage * pathLength);
,然后使用 pts.x
和 pts.y
作为 x 和 y 的属性,比如说,一个圆圈 SVG - 所以每次我向下(或向上)滚动页面时,圆圈的位置将 update/animate 沿着 SVG 路径。
我正在努力让它与 React 更具声明性的风格一起工作,因为我必须对圆和路径元素都使用 refs,这意味着我必须放置上述 pts = path.getPointAtLength...
代码在 useEffect 调用的 内部,两个引用都作为依赖项(否则引用将未定义,在这种情况下,x 和 y 上的 pts.x
和 pts.y
属性我的圈子 SVG 在第一次渲染时将无法访问。
有没有人解决过类似的问题或者可以提供指导?
对于这样一个简单的动画(圆点 旋转 )你可以使用简单的 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>
但是,如果您需要更通用的东西(例如,对于任意形状的轨迹),您可以使用与 post 中相同的方法,您指的是:
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>
在 vanilla Javascript 中有相对简单的方法来完成此操作(请参阅
Framer Motion 的 useViewPortScroll returns 一个方便的 scrollYProgress 对象,其“当前”属性 告诉您用户当前向下滚动页面的百分比。
我想用这个 属性 做这样的事情:const pts = path.getPointAtLength(scrollPercentage * pathLength);
,然后使用 pts.x
和 pts.y
作为 x 和 y 的属性,比如说,一个圆圈 SVG - 所以每次我向下(或向上)滚动页面时,圆圈的位置将 update/animate 沿着 SVG 路径。
我正在努力让它与 React 更具声明性的风格一起工作,因为我必须对圆和路径元素都使用 refs,这意味着我必须放置上述 pts = path.getPointAtLength...
代码在 useEffect 调用的 内部,两个引用都作为依赖项(否则引用将未定义,在这种情况下,x 和 y 上的 pts.x
和 pts.y
属性我的圈子 SVG 在第一次渲染时将无法访问。
有没有人解决过类似的问题或者可以提供指导?
对于这样一个简单的动画(圆点 旋转 )你可以使用简单的 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>
但是,如果您需要更通用的东西(例如,对于任意形状的轨迹),您可以使用与 post 中相同的方法,您指的是:
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>