Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 19x 19x 19x | import React, { ReactElement, useEffect, useRef } from "react"; export const CountDown = ({ startTime, currentProgress, isPlaying, size = 32, strokeWidth = 3, color = "#ffffff", }: { startTime: number; currentProgress: number; isPlaying: boolean; size?: number; strokeWidth?: number; color?: string; }): ReactElement | null => { const radius = size / 2 - strokeWidth / 2; const circumference = 2 * Math.PI * radius; const circleRef = useRef<SVGCircleElement>(null); const animationRef = useRef<number>(null); const progressRef = useRef(currentProgress); const lastTimeRef = useRef<number>(null); const updateCircle = () => { Iif (!circleRef.current) return; const remainingTime = startTime - progressRef.current; const totalDuration = startTime; const progress = Math.max(0, Math.min(1, remainingTime / totalDuration)); const dashOffset = circumference * (1 - progress); circleRef.current.style.strokeDashoffset = dashOffset.toString(); if (progress < 0.1) { circleRef.current.style.opacity = (progress * 10).toString(); // Fade out in last 10% } else { circleRef.current.style.opacity = "1"; } Iif (progress <= 0) { return false; } return true; }; useEffect(() => { progressRef.current = currentProgress; updateCircle(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentProgress, startTime]); useEffect(() => { const animate = (time: number) => { Iif (lastTimeRef.current && isPlaying) { const deltaTime = time - lastTimeRef.current; progressRef.current += deltaTime; } lastTimeRef.current = time; Iif (updateCircle()) { animationRef.current = requestAnimationFrame(animate); } }; Iif (isPlaying) { lastTimeRef.current = performance.now(); animationRef.current = requestAnimationFrame(animate); } return () => { Iif (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [isPlaying]); return ( <div className="countdown-container" style={{ width: size, height: size }}> <svg style={{ width: "100%", height: "100%" }} viewBox={`0 0 ${size} ${size}`} > <circle ref={circleRef} cx={size / 2} cy={size / 2} r={radius} fill="none" stroke={color} strokeWidth={strokeWidth} strokeDasharray={circumference} strokeDashoffset="0" transform={`rotate(-90 ${size / 2} ${size / 2})`} strokeLinecap="round" /> </svg> </div> ); }; export default CountDown; |