|
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", |
||||||
|
}} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||