92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import React, { useEffect, useState, useRef } from "react";
|
|
import { assetsPath } from "./Common";
|
|
import "./Bird.css";
|
|
|
|
const birdAngles = 12;
|
|
const frames = [0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
|
|
const useAnimationFrame = (callback: (t: number) => void) => {
|
|
const requestRef = useRef<number | null>(null);
|
|
const animate = (time: number) => {
|
|
callback(time);
|
|
requestRef.current = requestAnimationFrame(animate);
|
|
};
|
|
useEffect(() => {
|
|
requestRef.current = requestAnimationFrame(animate);
|
|
return () => {
|
|
if (requestRef.current !== null) {
|
|
cancelAnimationFrame(requestRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
};
|
|
|
|
const Bird: React.FC<{ radius: number; speed: number; size: number; style?: React.CSSProperties }> = ({
|
|
radius,
|
|
speed,
|
|
size,
|
|
style,
|
|
}) => {
|
|
const [time, setTime] = useState(0);
|
|
const [angle, setAngle] = useState(Math.random() * 360);
|
|
const [rotation] = useState((Math.PI * 2 * radius) / 5);
|
|
const [direction, setDirection] = useState(Math.floor((birdAngles * (angle ? angle : 0)) / 360));
|
|
const [cell, setCell] = useState(0);
|
|
const previousTimeRef = useRef<number | undefined>();
|
|
|
|
useAnimationFrame((t) => {
|
|
if (previousTimeRef.current !== undefined) {
|
|
const deltaTime = t - previousTimeRef.current;
|
|
setTime(deltaTime);
|
|
} else {
|
|
previousTimeRef.current = t;
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
const alpha = (time % speed) / speed;
|
|
const frame = Math.floor(frames.length * alpha);
|
|
const newAngle = (angle + rotation) % 360;
|
|
setAngle(newAngle);
|
|
setCell(frames[Math.floor(frame)]);
|
|
setDirection(Math.floor((birdAngles * newAngle) / 360));
|
|
}, [time, speed, rotation]);
|
|
|
|
return (
|
|
<div
|
|
className={`Bird`}
|
|
style={{
|
|
top: `${50 + 100 * radius * Math.sin((2 * Math.PI * (180 + angle)) / 360)}%`,
|
|
left: `${50 + 100 * radius * Math.cos((2 * Math.PI * (180 + angle)) / 360)}%`,
|
|
width: `${size * 64}px`,
|
|
height: `${size * 64}px`,
|
|
backgroundImage: `url(${assetsPath}/gfx/birds.png)`,
|
|
backgroundPositionX: `${(100 * direction) / 11}%`,
|
|
backgroundPositionY: `${(100 * cell) / 3}%`,
|
|
transformOrigin: `50% 50%`,
|
|
transform: `translate(-50%, -50%) rotate(${angle % 30}deg)`,
|
|
...style,
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const Flock: React.FC<{ count: number; style?: React.CSSProperties }> = ({ count, style }) => {
|
|
const [birds, setBirds] = useState<React.ReactNode[]>([]);
|
|
useEffect(() => {
|
|
const tmp: React.ReactNode[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const scalar = Math.random();
|
|
tmp.push(<Bird speed={2000 + 250 * scalar} size={0.2 + scalar * 0.25} radius={0.1 + scalar * 0.35} key={i} />);
|
|
}
|
|
setBirds(tmp);
|
|
}, [count]);
|
|
return (
|
|
<div className="Flock" style={style}>
|
|
{birds}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export { Bird, Flock };
|