File size: 3,272 Bytes
42da440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useMemo, useEffect, useRef } from 'react';
import { motion, useMotionValue, useTransform, useAnimationFrame } from 'framer-motion';

// Function to generate a random number within a range
const random = (min, max) => Math.random() * (max - min) + min;

// Create a single line element
const Line = ({ width, delay }) => (
  <motion.div
    className="h-1.5 bg-gray-600 rounded"
    style={{ width: `${width}%` }}
    initial={{ opacity: 0 }}
    animate={{ opacity: 0.4 }}
    transition={{ delay, duration: 0.5 }}
  />
);

// Create the scrolling background component
const ScrollingTextBackground = ({ duration = 30 }) => {
  const numLines = 50;

  const lines = useMemo(() => {
    return Array.from({ length: numLines }).map((_, i) => ({
      id: i,
      width: random(20, 80),
      delay: random(0, 1),
    }));
  }, []);

  // --- Manual Animation Logic ---
  const yProgress = useMotionValue(0); // Tracks progress from 0 to 1
  const yPercentage = useTransform(yProgress, [0, 1], ["0%", "-50%"]); // Maps progress to Y offset

  const lastFrameTime = useRef(performance.now());
  const animationFrameId = useRef(null); // To store the request ID
  const currentDuration = useRef(duration); // Store duration in a ref

  // Update the duration ref whenever the prop changes
  useEffect(() => {
    currentDuration.current = duration;
  }, [duration]);

  useAnimationFrame((timestamp) => {
    const deltaTime = timestamp - lastFrameTime.current;

    // Guard against potentially huge deltaTime after remount or tab backgrounding.
    // Only update animation if deltaTime is reasonable (e.g., < 1 second).
    // Also handle potential negative deltaTime if clocks adjust.
    if (deltaTime < 0 || deltaTime > 1000) {
        lastFrameTime.current = timestamp; // Reset for the next frame
        return; // Skip animation update for this frame
    }

    lastFrameTime.current = timestamp; // Update time for the next frame calculation

    // Calculate progress increment based on current duration
    // duration is in seconds, deltaTime in ms. Convert duration to ms.
    const progressIncrement = deltaTime / (currentDuration.current * 1000);

    // Update progress, wrapping around using modulo
    const newProgress = (yProgress.get() + progressIncrement) % 1;
    yProgress.set(newProgress);
  });

  // --- End Manual Animation Logic ---

  return (
    <div className="absolute inset-0 overflow-hidden z-0 pointer-events-none">
      <motion.div
        className="w-full"
        style={{ y: yPercentage }} // Apply the transformed y value directly
        // No animate, variants, or initial props needed for y animation here
      >
        {/* First block of lines */}
        <div className="w-full flex flex-col items-center space-y-4">
          {lines.map(line => (
            <Line key={`top-${line.id}`} width={line.width} delay={line.delay} />
          ))}
        </div>
        <br />
        {/* Second block of lines */}
        <div className="w-full flex flex-col items-center space-y-4">
          {lines.map(line => (
            <Line key={`bottom-${line.id}`} width={line.width} delay={line.delay} />
          ))}
        </div>
      </motion.div>
    </div>
  );
};

export default ScrollingTextBackground;