Skip to content

Instantly share code, notes, and snippets.

@Wxh16144
Created March 26, 2025 09:49
Show Gist options
  • Select an option

  • Save Wxh16144/c7b9c14d8427fecb0b287cd36cc0de26 to your computer and use it in GitHub Desktop.

Select an option

Save Wxh16144/c7b9c14d8427fecb0b287cd36cc0de26 to your computer and use it in GitHub Desktop.
贝塞尔曲线可视化
import React, { useEffect, useRef, useState } from 'react';
import { Flexbox } from 'react-layout-kit';
interface BezierVisualizerProps {
controls: [number, number, number, number]; // [x1, y1, x2, y2]
width?: number;
height?: number;
}
const BezierVisualizer: React.FC<BezierVisualizerProps> = ({
controls: [x1, y1, x2, y2],
width = 400,
height = 400,
}) => {
const [progress, setProgress] = useState(0);
const requestRef = useRef<number>();
const startTimeRef = useRef<number>(Date.now());
// 贝塞尔曲线参数方程
const getPosition = (t: number) => {
const u = 1 - t;
return {
x: 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t,
y: 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t,
};
};
// 动画循环
const animate = () => {
const elapsed = Date.now() - startTimeRef.current;
const t = (elapsed % 2000) / 2000; // 2秒循环
setProgress(t);
requestRef.current = requestAnimationFrame(animate);
};
useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current!);
}, []);
// 坐标转换到SVG视图
const scale = (val: number, axis: 'x' | 'y') =>
axis === 'x' ? val * width : height - val * height;
// 当前小球位置
const { x, y } = getPosition(progress);
const ballPos = {
x: scale(x, 'x'),
y: scale(y, 'y'),
};
return (
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
{/* 网格背景 */}
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#eee" strokeWidth="1" />
</pattern>
<rect width="100%" height="100%" fill="url(#grid)" />
{/* 坐标轴 */}
<path
d={`M 0 ${height} L ${width} ${height} M 0 ${height} L 0 0`}
stroke="#666"
strokeWidth="2"
/>
{/* 贝塞尔曲线路径 */}
<path
d={`
M 0 ${height}
C ${scale(x1, 'x')} ${scale(y1, 'y')},
${scale(x2, 'x')} ${scale(y2, 'y')},
${width} 0
`}
fill="none"
stroke="#2196f3"
strokeWidth="2"
/>
{/* 控制点连线 */}
<path
d={`
M 0 ${height}
L ${scale(x1, 'x')} ${scale(y1, 'y')}
L ${scale(x2, 'x')} ${scale(y2, 'y')}
L ${width} 0
`}
stroke="#666"
strokeDasharray="4 2"
fill="none"
/>
{/* 控制点 */}
<circle cx={scale(x1, 'x')} cy={scale(y1, 'y')} r="5" fill="#ff4757" />
<circle cx={scale(x2, 'x')} cy={scale(y2, 'y')} r="5" fill="#2ed573" />
{/* 运动小球 */}
<circle cx={ballPos.x} cy={ballPos.y} r="8" fill="#ffa502" />
</svg>
);
};
const App = () => {
return (
<Flexbox wrap="wrap" gap={16} horizontal>
<BezierVisualizer controls={[0.78, 0.14, 0.15, 0.86]} />
<BezierVisualizer controls={[0.755, 0.05, 0.855, 0.06]} />
<BezierVisualizer controls={[0.08, 0.82, 0.17, 1]} />
<BezierVisualizer controls={[0.23, 1, 0.32, 1]} />
</Flexbox>
);
};
export default App;
// export default BezierVisualizer;
@Wxh16144
Copy link
Author

Kapture.2025-03-26.at.17.49.54.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment