diff --git a/Components/animations/CloudAnimation.jsx b/Components/animations/CloudAnimation.jsx index 4711562..a7b57ea 100644 --- a/Components/animations/CloudAnimation.jsx +++ b/Components/animations/CloudAnimation.jsx @@ -1,8 +1,16 @@ "use client" import React from "react"; -import { motion, useScroll, useTransform, useSpring } from "framer-motion"; +import { motion, useScroll, useTransform, useSpring, useMotionTemplate } from "framer-motion"; import styles from "@/styles/Home.module.css"; +const CloudSVG = React.memo(function CloudSVG({ width = 150, height = 90 }) { + return ( + + + + ); +}); + export default function CloudAnimation() { const { scrollYProgress } = useScroll(); @@ -40,55 +48,29 @@ export default function CloudAnimation() { const rawY = useTransform(easedProgress, [0, 1], ["0vh", "20vh"]); const y = useSpring(rawY, { stiffness: 120, damping: 20 }); - // Horizontal movement (left to right, off-screen to off-screen) - const rawX = useTransform(scrollYProgress, [0, rotateInputEnd], ["-120vw", "120vw"]); - const x = useSpring(rawX, { stiffness: 120, damping: 20 }); + // Horizontal movement in numeric vw units (avoids string parsing each frame) + const rawXVw = useTransform(scrollYProgress, [0, rotateInputEnd], [-120, 120]); + const xVw = useSpring(rawXVw, { stiffness: 120, damping: 20 }); + const x = useMotionTemplate`${xVw}vw`; // Offset horizontal movement for second cloud - const x2 = useTransform(x, (xVal) => { - if (typeof xVal === 'string') { - const num = parseFloat(xVal); - return `${num * 0.7}vw`; - } - return `${Number(xVal) * 0.7}vw`; - }); + const x2Vw = useTransform(xVw, (xVal) => xVal * 0.7); + const x2 = useMotionTemplate`${x2Vw}vw`; // Create offset transforms for each cloud - const x3 = useTransform(x, (xVal) => { - if (typeof xVal === 'string') { - const num = parseFloat(xVal); - return `${num * 0.5}vw`; - } - return `${Number(xVal) * 0.5}vw`; - }); + const x3Vw = useTransform(xVw, (xVal) => xVal * 0.5); + const x3 = useMotionTemplate`${x3Vw}vw`; - const x4 = useTransform(x, (xVal) => { - if (typeof xVal === 'string') { - const num = parseFloat(xVal); - return `${num * 0.85}vw`; - } - return `${Number(xVal) * 0.85}vw`; - }); + const x4Vw = useTransform(xVw, (xVal) => xVal * 0.85); + const x4 = useMotionTemplate`${x4Vw}vw`; - const x5 = useTransform(x, (xVal) => { - if (typeof xVal === 'string') { - const num = parseFloat(xVal); - return `${num * 0.6}vw`; - } - return `${Number(xVal) * 0.6}vw`; - }); + const x5Vw = useTransform(xVw, (xVal) => xVal * 0.6); + const x5 = useMotionTemplate`${x5Vw}vw`; const startOffset = 55; const topPosition = `calc(50% - ${startOffset}px)`; - //Define cloud SVG (I totally did not do this LOL) - const CloudSVG = ({ width = 150, height = 90 }) => ( - - - - ); - // Cloud configurations: { scale, topOffset, xMotion, opacity, width, height } const cloudConfigs = [ { scale: 1.2, topOffset: 0, xMotion: x, opacity: 0.85, width: 180, height: 108 }, diff --git a/Components/animations/TurbineAnimation.jsx b/Components/animations/TurbineAnimation.jsx index 6198bf5..d1b80c9 100644 --- a/Components/animations/TurbineAnimation.jsx +++ b/Components/animations/TurbineAnimation.jsx @@ -1,12 +1,14 @@ "use client" import React from "react"; -import { motion, useScroll, useTransform, useSpring } from "framer-motion"; +import { motion, useScroll, useTransform, useSpring, useMotionValueEvent } from "framer-motion"; import TurbineBlades from "./TurbineBlades"; import CloudAnimation from "./CloudAnimation"; import styles from "@/styles/Home.module.css"; export default function TurbineScrollAnimation() { const { scrollYProgress } = useScroll(); + const mainAboutRef = React.useRef(null); + const aboutVisibleRef = React.useRef(false); // progress value (0..1) at which the about section reaches the top of the // viewport. We'll compute this on mount and on resize so we can map the @@ -64,6 +66,7 @@ export default function TurbineScrollAnimation() { const rotateInputEnd = triggerProgress > 0 ? Math.max(0.01, triggerProgress * completionMultiplier) : completionMultiplier; + const rotateInputEndRef = React.useRef(rotateInputEnd); const localProgress = useTransform(scrollYProgress, [0, rotateInputEnd], [0, 1]); const easedProgress = useTransform(localProgress, (v) => Math.pow(v, easePower)); @@ -78,33 +81,35 @@ export default function TurbineScrollAnimation() { const rotate = useSpring(rawRotate, { stiffness: 120, damping: 20 }); React.useEffect(() => { + mainAboutRef.current = document.querySelector("." + styles.mainAbout); + }, []); + + useMotionValueEvent(rotate, "change", (v) => { const revealThreshold = 0.9 * 1080; // 90% of 1080deg - const unsubscribe = rotate.onChange((v) => { - const aboutEl = document.querySelector("." + styles.mainAbout); - if (!aboutEl) return; - if (v >= revealThreshold) { - if (!aboutEl.classList.contains(styles.mainAboutVisible)) { - aboutEl.classList.add(styles.mainAboutVisible); - } - } else { - if (aboutEl.classList.contains(styles.mainAboutVisible)) { - aboutEl.classList.remove(styles.mainAboutVisible); - } - } - }); - return () => unsubscribe(); - }, [rotate]); + const aboutEl = mainAboutRef.current || document.querySelector("." + styles.mainAbout); + if (!aboutEl) return; + mainAboutRef.current = aboutEl; + const shouldShow = v >= revealThreshold; + if (aboutVisibleRef.current !== shouldShow) { + aboutVisibleRef.current = shouldShow; + aboutEl.classList.toggle(styles.mainAboutVisible, shouldShow); + } + }); // Position turbine to align with the center of the logo in the hero section // Hero section is 60vh, and logo is centered within it // We want the turbine at roughly 30vh from top (center of 60vh hero section) - const startOffset = 0; const topPosition = `40vh`; // Track if animation is complete and the absolute position to stick to const [isAnimationComplete, setIsAnimationComplete] = React.useState(false); + const isAnimationCompleteRef = React.useRef(false); const [stickyTop, setStickyTop] = React.useState(0); + React.useEffect(() => { + rotateInputEndRef.current = rotateInputEnd; + }, [rotateInputEnd]); + React.useEffect(() => { // Calculate the sticky position once on mount/resize, not based on scroll function computeStickyPosition() { @@ -136,20 +141,19 @@ export default function TurbineScrollAnimation() { window.addEventListener("resize", computeStickyPosition); const t = setTimeout(computeStickyPosition, 300); - const unsubscribe = scrollYProgress.onChange((v) => { - if (v >= rotateInputEnd && !isAnimationComplete) { - setIsAnimationComplete(true); - } else if (v < rotateInputEnd && isAnimationComplete) { - setIsAnimationComplete(false); - } - }); - return () => { - unsubscribe(); window.removeEventListener("resize", computeStickyPosition); clearTimeout(t); }; - }, [scrollYProgress, rotateInputEnd, isAnimationComplete, startOffset]); + }, []); + + useMotionValueEvent(scrollYProgress, "change", (v) => { + const nextComplete = v >= rotateInputEndRef.current; + if (isAnimationCompleteRef.current !== nextComplete) { + isAnimationCompleteRef.current = nextComplete; + setIsAnimationComplete(nextComplete); + } + }); return ( <>