|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,145 @@
|
||||
import { useEffect, useRef } from "react"; |
||||
|
||||
export default function MeshWaveBg() { |
||||
const canvasRef = useRef(null); |
||||
|
||||
useEffect(() => { |
||||
const canvas = canvasRef.current; |
||||
const ctx = canvas.getContext("2d"); |
||||
let raf; |
||||
let t = 0; |
||||
|
||||
const COLS = 18; |
||||
const ROWS = 10; |
||||
const COLORS = ["#d94889", "#7b3fa0", "#593a84", "#198dc7", "#1a1f5e"]; |
||||
|
||||
function resize() { |
||||
canvas.width = canvas.offsetWidth; |
||||
canvas.height = canvas.offsetHeight; |
||||
} |
||||
|
||||
function getPoint(col, row, w, h) { |
||||
const cellW = w / (COLS - 1); |
||||
const cellH = h / (ROWS - 1); |
||||
const baseX = col * cellW; |
||||
const baseY = row * cellH; |
||||
|
||||
const waveX = |
||||
Math.sin(t * 0.6 + row * 0.7 + col * 0.3) * cellW * 0.28 + |
||||
Math.sin(t * 0.3 + col * 0.5) * cellW * 0.12; |
||||
const waveY = |
||||
Math.sin(t * 0.5 + col * 0.6 + row * 0.4) * cellH * 0.38 + |
||||
Math.cos(t * 0.4 + row * 0.5) * cellH * 0.14; |
||||
|
||||
return { x: baseX + waveX, y: baseY + waveY }; |
||||
} |
||||
|
||||
function draw() { |
||||
const w = canvas.width; |
||||
const h = canvas.height; |
||||
ctx.clearRect(0, 0, w, h); |
||||
|
||||
t += 0.008; |
||||
|
||||
const pts = []; |
||||
for (let r = 0; r < ROWS; r++) { |
||||
pts[r] = []; |
||||
for (let c = 0; c < COLS; c++) { |
||||
pts[r][c] = getPoint(c, r, w, h); |
||||
} |
||||
} |
||||
|
||||
// 메시 선 |
||||
for (let r = 0; r < ROWS; r++) { |
||||
for (let c = 0; c < COLS; c++) { |
||||
const p = pts[r][c]; |
||||
const colorIdx = (r * COLS + c) % COLORS.length; |
||||
const alpha = 0.06 + ((Math.sin(t + r + c) + 1) / 2) * 0.07; |
||||
|
||||
// 가로 선 |
||||
if (c < COLS - 1) { |
||||
const p2 = pts[r][c + 1]; |
||||
const grad = ctx.createLinearGradient(p.x, p.y, p2.x, p2.y); |
||||
grad.addColorStop(0, COLORS[colorIdx] + toHex(alpha)); |
||||
grad.addColorStop( |
||||
1, |
||||
COLORS[(colorIdx + 1) % COLORS.length] + toHex(alpha), |
||||
); |
||||
ctx.beginPath(); |
||||
ctx.moveTo(p.x, p.y); |
||||
ctx.lineTo(p2.x, p2.y); |
||||
ctx.strokeStyle = grad; |
||||
ctx.lineWidth = 0.7; |
||||
ctx.stroke(); |
||||
} |
||||
// 세로 선 |
||||
if (r < ROWS - 1) { |
||||
const p2 = pts[r + 1][c]; |
||||
const grad = ctx.createLinearGradient(p.x, p.y, p2.x, p2.y); |
||||
grad.addColorStop(0, COLORS[colorIdx] + toHex(alpha)); |
||||
grad.addColorStop( |
||||
1, |
||||
COLORS[(colorIdx + 2) % COLORS.length] + toHex(alpha), |
||||
); |
||||
ctx.beginPath(); |
||||
ctx.moveTo(p.x, p.y); |
||||
ctx.lineTo(p2.x, p2.y); |
||||
ctx.strokeStyle = grad; |
||||
ctx.lineWidth = 0.7; |
||||
ctx.stroke(); |
||||
} |
||||
// 대각선 |
||||
if (c < COLS - 1 && r < ROWS - 1) { |
||||
const p2 = pts[r + 1][c + 1]; |
||||
ctx.beginPath(); |
||||
ctx.moveTo(p.x, p.y); |
||||
ctx.lineTo(p2.x, p2.y); |
||||
ctx.strokeStyle = COLORS[colorIdx] + toHex(alpha * 0.5); |
||||
ctx.lineWidth = 0.4; |
||||
ctx.stroke(); |
||||
} |
||||
|
||||
// 교차점 노드 |
||||
const nodeAlpha = |
||||
0.12 + ((Math.sin(t * 1.2 + r * 1.3 + c * 0.9) + 1) / 2) * 0.18; |
||||
ctx.beginPath(); |
||||
ctx.arc(p.x, p.y, 1.2, 0, Math.PI * 2); |
||||
ctx.fillStyle = COLORS[colorIdx] + toHex(nodeAlpha); |
||||
ctx.fill(); |
||||
} |
||||
} |
||||
|
||||
raf = requestAnimationFrame(draw); |
||||
} |
||||
|
||||
function toHex(alpha) { |
||||
return Math.round(Math.min(1, Math.max(0, alpha)) * 255) |
||||
.toString(16) |
||||
.padStart(2, "0"); |
||||
} |
||||
|
||||
resize(); |
||||
draw(); |
||||
|
||||
const ro = new ResizeObserver(resize); |
||||
ro.observe(canvas); |
||||
|
||||
return () => { |
||||
cancelAnimationFrame(raf); |
||||
ro.disconnect(); |
||||
}; |
||||
}, []); |
||||
|
||||
return ( |
||||
<canvas |
||||
ref={canvasRef} |
||||
style={{ |
||||
position: "absolute", |
||||
inset: 0, |
||||
width: "100%", |
||||
height: "100%", |
||||
pointerEvents: "none", |
||||
}} |
||||
/> |
||||
); |
||||
} |
||||