lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
74 lines (73 loc) • 3.48 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { cn } from "../../lib/utils";
/**
* RollingText3D - A premium 3D text rotation component.
* Replicates the "Tube" rotation effect using GSAP.
*/
export const RollingText3D = ({ text, fontSize = "8vw", color = "currentColor", className, duration = 0.9, stagger = 0.08, perspective = 700, letterSpacing = "-0.05em", linesCount = 4, }) => {
const containerRef = useRef(null);
const tubeRef = useRef(null);
useEffect(() => {
if (!containerRef.current || !tubeRef.current)
return;
const container = containerRef.current;
const lines = tubeRef.current.querySelectorAll(".rolling-line");
// Set initial visibility
gsap.set(container, { visibility: "visible" });
// Manually split text into characters for each line
// This allows us to avoid the paid GSAP SplitText plugin while keeping the effect.
lines.forEach((line) => {
const content = line.textContent || "";
line.innerHTML = "";
content.split("").forEach((char) => {
const span = document.createElement("span");
span.textContent = char === " " ? "\u00A0" : char;
span.style.display = "inline-block";
span.style.backfaceVisibility = "hidden";
line.appendChild(span);
});
});
const allLinesChars = Array.from(lines).map((line) => Array.from(line.querySelectorAll("span")));
// 3D setup logic
const update3D = () => {
const width = window.innerWidth;
const depth = -width / 8;
const transformOrigin = `50% 50% ${depth}px`;
gsap.set(lines, { perspective, transformStyle: "preserve-3d" });
const tl = gsap.timeline({ repeat: -1 });
allLinesChars.forEach((chars, index) => {
tl.fromTo(chars, { rotationX: -90, opacity: 0 }, {
rotationX: 90,
opacity: 1,
stagger,
duration,
ease: "none",
transformOrigin
}, index * (duration / 2) // staggered entry for each line
);
});
return tl;
};
const timeline = update3D();
// Handle resize
const handleResize = () => {
timeline.kill();
update3D();
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
timeline.kill();
};
}, [text, duration, stagger, perspective, linesCount]);
return (_jsx("div", { ref: containerRef, className: cn("relative flex items-center justify-center w-full h-full overflow-hidden select-none", className), style: { visibility: "hidden" }, children: _jsx("div", { ref: tubeRef, className: "relative w-full text-center", style: { height: "1.2em" }, children: Array.from({ length: linesCount }).map((_, i) => (_jsx("h1", { className: "rolling-line absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 m-0 whitespace-nowrap", style: {
fontSize,
color,
letterSpacing,
lineHeight: 1,
}, children: text }, i))) }) }));
};