我需要创建一个页面来根据里程碑显示用户的计划进度。为了实现曲线路径是使用从“react-native-svg”导入的“路径”创建的,现在进度图标应该沿着路径进行动画/移动。
红色标记应根据他/她在程序中的进度沿着曲线移动到某个特定点。
注意 - 我已经创建了路径。我所需要的只是标记的动画
这是一个有趣的问题,同时利用了几何和动态渲染技术。我们可以利用
SVG
或 React Native 的矢量绘图库的功能,并且可以根据给定的 progress percentage
动态计算进度图标沿曲线路径的位置。
实施于
React-native
:
import React, { useEffect, useState, useRef } from 'react';
import { Path, Svg, Circle, G } from 'react-native-svg';
import { View, Button, Text } from 'react-native';
const ProgressPath = ({ progress }) => {
const [progressPosition, setProgressPosition] = useState({ x: 0, y: 0 });
const ref = useRef(null);
useEffect(() => {
if(!ref.current) return;
const path = ref.current;
const pathLength = path.getTotalLength();
const position = path.getPointAtLength(pathLength * progress);
setProgressPosition({ x: position.x, y: position.y });
}, [progress]);
return (
<Svg width="400" height="500">
{/* Path */}
<Path
ref={ref}
id="curve"
fill="none"
stroke="black"
strokeWidth="6"
d="M 200,50
C 250,100 150,100 200,150
C 250,200 150,200 200,250
C 250,300 150,300 200,350
C 250,400 150,400 200,450"
/>
{/* Marker */}
<Circle
cx={progressPosition.x}
cy={progressPosition.y}
r="5"
fill="red"
/>
</Svg>
);
};
const App = () => {
const [progress, setProgress] = useState(0.5);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>{`Progress: ${Math.floor(progress * 100)}%`}</Text>
<ProgressPath progress={progress} />
<Button
title="Decrease Progress"
onPress={() => setProgress((prev) => prev - 0.1)}
/>
<Button
title="Increase Progress"
onPress={() => setProgress((prev) => prev + 0.1)}
/>
</View>
);
};
export default App;
另外,我也在
React
中创作。唯一的主要变化是使用 document.getElementById
而不是 ref
。这是实现:
import React, { useEffect, useState } from "react";
const ProgressPath = ({ progress }) => {
const [progressPosition, setProgressPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const path = document.getElementById("curve");
const pathLength = path.getTotalLength();
const position = path.getPointAtLength(pathLength * progress);
setProgressPosition({ x: position.x, y: position.y });
}, [progress]);
return (
<svg width="1000" height="500">
{/* Path */}
<path
id="curve"
fill="none"
stroke="black"
strokeWidth="6"
d="
M 200,50
C 250,100 150,100 200,150
C 250,200 150,200 200,250
C 250,300 150,300 200,350
C 250,400 150,400 200,450
"
/>
{/* Marker */}
<circle
cx={progressPosition.x}
cy={progressPosition.y}
r="5"
fill="red"
/>
</svg>
);
};
const App = () => {
const [progress, setProgress] = useState(0.5);
return (
<div>
<h1>{`Progress: ${Math.floor(progress * 100)}%`}</h1>
<ProgressPath progress={progress} />
<button onClick={() => setProgress((prev) => prev - 0.1)}>
Descrease Progress
</button>
<button onClick={() => setProgress((prev) => prev + 0.1)}>
Increase Progress
</button>
</div>
);
};
export default App;
我们可以通过在一段小时间间隔(例如50ms)内逐渐增加/减少运动0.1%来实现平滑的动画。这是相同的实现:
存储之前的进度:
const App = () => {
const [prevProgress, setPrevProgress] = useState(0.5);
const [progress, setProgress] = useState(0.5);
return (
<div>
<h1>{`Progress: ${Math.floor(progress * 100)}%`}</h1>
<ProgressPath prevProgress={prevProgress} progress={progress} />
<button
onClick={() => {
setPrevProgress(progress);
setProgress((prev) => prev - 0.1);
}}
>
Descrease Progress
</button>
<button
onClick={() => {
setPrevProgress(progress);
setProgress((prev) => prev + 0.1);
}}
>
Increase Progress
</button>
</div>
);
};
定义间隔:
const interval_in_ms = 50;
进展路径:
const ProgressPath = ({ prevProgress, progress }) => {
const [progressPosition, setProgressPosition] = useState({ x: 0, y: 0 });
//This will make the position change gradually depending on the value of `interval_in_ms`.
function animatePositionChange(
path,
prevProgress,
progress,
setProgressPosition
) {
const pathLength = path.getTotalLength();
var partialProgress = prevProgress;
const intervalId = setInterval(() => {
if (prevProgress < progress) {
if (partialProgress > progress) {
clearInterval(intervalId);
}
partialProgress += 0.01;
} else {
if (partialProgress < progress) {
clearInterval(intervalId);
}
partialProgress -= 0.01;
}
const position = path.getPointAtLength(pathLength * partialProgress);
setProgressPosition({ x: position.x, y: position.y });
}, interval_in_ms);
}
useEffect(() => {
const path = document.getElementById("curve");
animatePositionChange(path, prevProgress, progress, setProgressPosition);
}, [progress]);
return (
<svg width="1000" height="500">
{/* Path */}
<path
id="curve"
fill="none"
stroke="black"
strokeWidth="6"
d="
M 200,50
C 250,100 150,100 200,150
C 250,200 150,200 200,250
C 250,300 150,300 200,350
C 250,400 150,400 200,450
"
/>
{/* Marker */}
<circle
style={{
transition: `all ${interval_in_ms}ms linear`,
willChange: "cx, cy",
}}
cx={progressPosition.x}
cy={progressPosition.y}
r="5"
fill="red"
/>
</svg>
);
};
注:
transition: all ${interval_in_ms}ms linear
这负责平稳过渡
willChange: cx, cy
;
CSS 属性 will-change 向浏览器提示元素将如何变化。浏览器可能会在元素实际更改之前设置优化。