From 49c195032b8ca21e4753e510d837a742ab08327d Mon Sep 17 00:00:00 2001 From: Malay Vasa Date: Wed, 9 Oct 2024 14:47:00 +0530 Subject: [PATCH] feat(dataroom): sine wave animation (#441) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Open branch * feat: 🎸 Add See More Section * refactor: 💡 address PR comments * fix: 🐛 address PR comments + import framer motion on client * fix: 🐛 address console error * refactor: 💡 update animation to use canvas instead of svg * fix: remove duplicated section --------- Co-authored-by: iamacook Co-authored-by: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> --- .../DataRoom/SeeMore/SlidingWave.tsx | 37 ++++++ src/components/DataRoom/SeeMore/Wave.tsx | 107 ++++++++++++++++++ src/components/DataRoom/SeeMore/index.tsx | 6 +- .../DataRoom/SeeMore/styles.module.css | 52 +++++++++ 4 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 src/components/DataRoom/SeeMore/SlidingWave.tsx create mode 100644 src/components/DataRoom/SeeMore/Wave.tsx diff --git a/src/components/DataRoom/SeeMore/SlidingWave.tsx b/src/components/DataRoom/SeeMore/SlidingWave.tsx new file mode 100644 index 000000000..fbf04f88c --- /dev/null +++ b/src/components/DataRoom/SeeMore/SlidingWave.tsx @@ -0,0 +1,37 @@ +import type { BaseBlock } from '@/components/Home/types' +import { useIsMediumScreen } from '@/hooks/useMaxWidth' +import { Typography } from '@mui/material' +import { motion, useScroll, useTransform } from 'framer-motion' +import type { RefObject } from 'react' +import css from './styles.module.css' +import Wave from './Wave' + +export default function SlidingWave({ + text, + containerRef, +}: { + text: BaseBlock['text'] + containerRef: RefObject +}) { + const isMobile = useIsMediumScreen() + const { scrollYProgress } = useScroll({ + target: containerRef, + offset: ['start end', 'end start'], + }) + + const opacity = useTransform(scrollYProgress, [0, 0.3, 0.6, 0.75], [0, 1, 1, 0]) + const textOpacity = useTransform(scrollYProgress, [0, 0.3, 0.5, 0.75], [0, 1, 1, 0]) + const translate = useTransform(scrollYProgress, [0, 0.2, 0.55, 0.75], ['-250px', '0px', '0px', '250px']) + + return ( + <> + + {text} + +
+ + + + + ) +} diff --git a/src/components/DataRoom/SeeMore/Wave.tsx b/src/components/DataRoom/SeeMore/Wave.tsx new file mode 100644 index 000000000..ecad432c4 --- /dev/null +++ b/src/components/DataRoom/SeeMore/Wave.tsx @@ -0,0 +1,107 @@ +import useContainerSize from '@/hooks/useContainerSize' +import React, { useEffect, useRef, useMemo, useCallback } from 'react' + +type WaveProps = { + height?: number + colors?: string[] + amplitude?: number + frequency?: number + speed?: number +} + +const DEFAULT_HEIGHT = 600 +const DEFAULT_COLORS = ['#12FF80', '#008A40', '#003C1C'] +const DEFAULT_AMPLITUDE = 100 +const DEFAULT_FREQUENCY = 1 +const DEFAULT_SPEED = 0.8 +const STROKE_WIDTH = 4 + +const Wave = ({ + height = DEFAULT_HEIGHT, + colors = DEFAULT_COLORS, + amplitude = DEFAULT_AMPLITUDE, + frequency = DEFAULT_FREQUENCY, + speed = DEFAULT_SPEED, +}: WaveProps) => { + const canvasRef = useRef(null) + const containerRef = useRef(null) + const { width } = useContainerSize(containerRef) + const animationRef = useRef() + + const dpr = useMemo(() => window.devicePixelRatio || 1, []) + + const paths = useMemo(() => { + if (width === 0) return [[], [], []] + const calculatePath = (amplitudeOffset: number) => + Array.from( + { length: width + 1 }, + (_, i) => (amplitude - amplitudeOffset) * Math.sin((i / width) * 2 * Math.PI * frequency), + ) + return [calculatePath(80), calculatePath(40), calculatePath(0)] + }, [width, amplitude, frequency]) + + const setupCanvas = useCallback(() => { + const canvas = canvasRef.current + const ctx = canvas?.getContext('2d') + if (!ctx || !canvas || width === 0) return + + canvas.width = width * dpr + canvas.height = height * dpr + canvas.style.width = `${width}px` + canvas.style.height = `${height}px` + ctx.scale(dpr, dpr) + }, [width, height, dpr]) + + const drawWave = useCallback( + (ctx: CanvasRenderingContext2D, path: number[], color: string, shift: number) => { + ctx.beginPath() + ctx.lineWidth = STROKE_WIDTH + ctx.strokeStyle = color + + for (let i = 0; i <= width; i++) { + ctx.lineTo(i, height / 2 + path[(i + Math.floor(shift)) % width]) + } + + ctx.stroke() + }, + [width, height], + ) + + const animate = useCallback( + (time: number) => { + const canvas = canvasRef.current + const ctx = canvas?.getContext('2d') + if (!ctx || !canvas) return + + const shift = (time * speed) % width + + ctx.clearRect(0, 0, width, height) + + paths.forEach((path, index) => { + drawWave(ctx, path, colors[index % colors.length], shift) + }) + + animationRef.current = requestAnimationFrame(animate) + }, + [width, height, paths, speed, colors, drawWave], + ) + + useEffect(() => { + setupCanvas() + animationRef.current = requestAnimationFrame(animate) + + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current) + } + } + }, [setupCanvas, animate]) + + return ( +
+ +
+ ) +} + +export default Wave diff --git a/src/components/DataRoom/SeeMore/index.tsx b/src/components/DataRoom/SeeMore/index.tsx index db3a49a5a..46b0bd6eb 100644 --- a/src/components/DataRoom/SeeMore/index.tsx +++ b/src/components/DataRoom/SeeMore/index.tsx @@ -3,14 +3,16 @@ import { useRef } from 'react' import dynamic from 'next/dynamic' import css from './styles.module.css' -const SlidingContent = dynamic(() => import('./SlidingContent')) +const SlidingWave = dynamic(() => import('./SlidingWave')) const SeeMore = ({ text }: BaseBlock) => { const backgroundRef = useRef(null) return (
- +
+ +
) } diff --git a/src/components/DataRoom/SeeMore/styles.module.css b/src/components/DataRoom/SeeMore/styles.module.css index 778378787..42c5c7a00 100644 --- a/src/components/DataRoom/SeeMore/styles.module.css +++ b/src/components/DataRoom/SeeMore/styles.module.css @@ -6,7 +6,59 @@ align-items: center; } +.stickyContainer { + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: sticky; + top: 0; +} + +.text { + z-index: 50; +} + +.gradientBox { + width: 100%; + height: 100%; + background: linear-gradient( + to right, + #121312 0%, + rgba(18, 19, 18, 0.8) 40%, + rgba(18, 19, 18, 0.4) 70%, + transparent 100% + ); + z-index: 40; + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +.wave { + position: absolute; + top: 50%; + width: 100%; + transform: translateY(-50%); + right: 0; + z-index: 30; +} + @media (max-width: 900px) { + .text { + padding: 60px; + margin-top: 120px; + text-align: center; + } + .stickyContainer { + justify-content: start; + } + + .wave { + transform: translateY(-25%); + } + .sectionContainer { height: 60lvh; }