Skip to content

Commit

Permalink
feat(dataroom): sine wave animation (#441)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Diogo Soares <[email protected]>
  • Loading branch information
3 people authored Oct 9, 2024
1 parent 3c36729 commit 49c1950
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 2 deletions.
37 changes: 37 additions & 0 deletions src/components/DataRoom/SeeMore/SlidingWave.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>
}) {
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 (
<>
<motion.div style={{ opacity: textOpacity, translateY: translate }} className={css.text}>
<Typography variant="h1">{text}</Typography>
</motion.div>
<div className={css.gradientBox}></div>
<motion.div style={{ opacity }} className={css.wave}>
<Wave colors={['#003C1C', '#008A40', '#12FF80']} amplitude={isMobile ? 100 : 200} />
</motion.div>
</>
)
}
107 changes: 107 additions & 0 deletions src/components/DataRoom/SeeMore/Wave.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLCanvasElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const { width } = useContainerSize(containerRef)
const animationRef = useRef<number>()

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 (
<div ref={containerRef} style={{ width: '100%' }}>
<canvas ref={canvasRef} />
</div>
)
}

export default Wave
6 changes: 4 additions & 2 deletions src/components/DataRoom/SeeMore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>(null)

return (
<div ref={backgroundRef} className={css.sectionContainer}>
<SlidingContent text={text} containerRef={backgroundRef} />
<div className={css.stickyContainer}>
<SlidingWave text={text} containerRef={backgroundRef} />
</div>
</div>
)
}
Expand Down
52 changes: 52 additions & 0 deletions src/components/DataRoom/SeeMore/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 49c1950

Please sign in to comment.