mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
12 KiB
JSON
1 line
12 KiB
JSON
{"name":"Waves","title":"Waves","description":"Layered lines that form smooth wave patterns with animation.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div\n ref=\"containerRef\"\n :class=\"className\"\n :style=\"{ backgroundColor, ...style }\"\n class=\"absolute top-0 left-0 w-full h-full overflow-hidden\"\n >\n <div\n class=\"absolute top-0 left-0 bg-[#160000] rounded-full w-[0.5rem] h-[0.5rem]\"\n :style=\"{\n transform: 'translate3d(calc(var(--x) - 50%), calc(var(--y) - 50%), 0)',\n willChange: 'transform'\n }\"\n />\n\n <canvas ref=\"canvasRef\" class=\"block w-full h-full\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, type CSSProperties, useTemplateRef } from 'vue';\n\nclass Grad {\n x: number;\n y: number;\n z: number;\n\n constructor(x: number, y: number, z: number) {\n this.x = x;\n this.y = y;\n this.z = z;\n }\n\n dot2(x: number, y: number): number {\n return this.x * x + this.y * y;\n }\n}\n\nclass Noise {\n grad3: Grad[];\n p: number[];\n perm: number[];\n gradP: Grad[];\n\n constructor(seed = 0) {\n this.grad3 = [\n new Grad(1, 1, 0),\n new Grad(-1, 1, 0),\n new Grad(1, -1, 0),\n new Grad(-1, -1, 0),\n new Grad(1, 0, 1),\n new Grad(-1, 0, 1),\n new Grad(1, 0, -1),\n new Grad(-1, 0, -1),\n new Grad(0, 1, 1),\n new Grad(0, -1, 1),\n new Grad(0, 1, -1),\n new Grad(0, -1, -1)\n ];\n\n this.p = [\n 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240,\n 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,\n 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83,\n 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216,\n 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186,\n 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,\n 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,\n 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,\n 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,\n 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128,\n 195, 78, 66, 215, 61, 156, 180\n ];\n\n this.perm = new Array(512);\n this.gradP = new Array(512);\n this.seed(seed);\n }\n\n seed(seed: number) {\n if (seed > 0 && seed < 1) seed *= 65536;\n seed = Math.floor(seed);\n if (seed < 256) seed |= seed << 8;\n\n for (let i = 0; i < 256; i++) {\n const v = i & 1 ? this.p[i] ^ (seed & 255) : this.p[i] ^ ((seed >> 8) & 255);\n this.perm[i] = this.perm[i + 256] = v;\n this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12];\n }\n }\n\n fade(t: number): number {\n return t * t * t * (t * (t * 6 - 15) + 10);\n }\n\n lerp(a: number, b: number, t: number): number {\n return (1 - t) * a + t * b;\n }\n\n perlin2(x: number, y: number): number {\n let X = Math.floor(x),\n Y = Math.floor(y);\n x -= X;\n y -= Y;\n X &= 255;\n Y &= 255;\n\n const n00 = this.gradP[X + this.perm[Y]].dot2(x, y);\n const n01 = this.gradP[X + this.perm[Y + 1]].dot2(x, y - 1);\n const n10 = this.gradP[X + 1 + this.perm[Y]].dot2(x - 1, y);\n const n11 = this.gradP[X + 1 + this.perm[Y + 1]].dot2(x - 1, y - 1);\n const u = this.fade(x);\n\n return this.lerp(this.lerp(n00, n10, u), this.lerp(n01, n11, u), this.fade(y));\n }\n}\n\ninterface Point {\n x: number;\n y: number;\n wave: { x: number; y: number };\n cursor: { x: number; y: number; vx: number; vy: number };\n}\n\ninterface Mouse {\n x: number;\n y: number;\n lx: number;\n ly: number;\n sx: number;\n sy: number;\n v: number;\n vs: number;\n a: number;\n set: boolean;\n}\n\ninterface Config {\n lineColor: string;\n waveSpeedX: number;\n waveSpeedY: number;\n waveAmpX: number;\n waveAmpY: number;\n friction: number;\n tension: number;\n maxCursorMove: number;\n xGap: number;\n yGap: number;\n}\n\ninterface WavesProps {\n lineColor?: string;\n backgroundColor?: string;\n waveSpeedX?: number;\n waveSpeedY?: number;\n waveAmpX?: number;\n waveAmpY?: number;\n xGap?: number;\n yGap?: number;\n friction?: number;\n tension?: number;\n maxCursorMove?: number;\n style?: CSSProperties;\n className?: string;\n}\n\nconst props = withDefaults(defineProps<WavesProps>(), {\n lineColor: 'black',\n backgroundColor: 'transparent',\n waveSpeedX: 0.0125,\n waveSpeedY: 0.005,\n waveAmpX: 32,\n waveAmpY: 16,\n xGap: 10,\n yGap: 32,\n friction: 0.925,\n tension: 0.005,\n maxCursorMove: 100,\n style: () => ({}),\n className: ''\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\n\nlet ctx: CanvasRenderingContext2D | null = null;\nlet bounding = { width: 0, height: 0, left: 0, top: 0 };\nlet noise: Noise | null = null;\nlet lines: Point[][] = [];\nconst mouse: Mouse = {\n x: -10,\n y: 0,\n lx: 0,\n ly: 0,\n sx: 0,\n sy: 0,\n v: 0,\n vs: 0,\n a: 0,\n set: false\n};\nlet config: Config = {\n lineColor: props.lineColor,\n waveSpeedX: props.waveSpeedX,\n waveSpeedY: props.waveSpeedY,\n waveAmpX: props.waveAmpX,\n waveAmpY: props.waveAmpY,\n friction: props.friction,\n tension: props.tension,\n maxCursorMove: props.maxCursorMove,\n xGap: props.xGap,\n yGap: props.yGap\n};\nlet frameId: number | null = null;\n\nconst setSize = () => {\n const container = containerRef.value;\n const canvas = canvasRef.value;\n if (!container || !canvas) return;\n\n const rect = container.getBoundingClientRect();\n bounding = {\n width: rect.width,\n height: rect.height,\n left: rect.left,\n top: rect.top\n };\n canvas.width = rect.width;\n canvas.height = rect.height;\n};\n\nconst setLines = () => {\n const { width, height } = bounding;\n lines = [];\n const oWidth = width + 200,\n oHeight = height + 30;\n const { xGap, yGap } = config;\n const totalLines = Math.ceil(oWidth / xGap);\n const totalPoints = Math.ceil(oHeight / yGap);\n const xStart = (width - xGap * totalLines) / 2;\n const yStart = (height - yGap * totalPoints) / 2;\n\n for (let i = 0; i <= totalLines; i++) {\n const pts: Point[] = [];\n for (let j = 0; j <= totalPoints; j++) {\n pts.push({\n x: xStart + xGap * i,\n y: yStart + yGap * j,\n wave: { x: 0, y: 0 },\n cursor: { x: 0, y: 0, vx: 0, vy: 0 }\n });\n }\n lines.push(pts);\n }\n};\n\nconst movePoints = (time: number) => {\n if (!noise) return;\n\n const { waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove } = config;\n\n lines.forEach(pts => {\n pts.forEach(p => {\n const move = noise!.perlin2((p.x + time * waveSpeedX) * 0.002, (p.y + time * waveSpeedY) * 0.0015) * 12;\n p.wave.x = Math.cos(move) * waveAmpX;\n p.wave.y = Math.sin(move) * waveAmpY;\n\n const dx = p.x - mouse.sx,\n dy = p.y - mouse.sy;\n const dist = Math.hypot(dx, dy);\n const l = Math.max(175, mouse.vs);\n if (dist < l) {\n const s = 1 - dist / l;\n const f = Math.cos(dist * 0.001) * s;\n p.cursor.vx += Math.cos(mouse.a) * f * l * mouse.vs * 0.00065;\n p.cursor.vy += Math.sin(mouse.a) * f * l * mouse.vs * 0.00065;\n }\n\n p.cursor.vx += (0 - p.cursor.x) * tension;\n p.cursor.vy += (0 - p.cursor.y) * tension;\n p.cursor.vx *= friction;\n p.cursor.vy *= friction;\n p.cursor.x += p.cursor.vx * 2;\n p.cursor.y += p.cursor.vy * 2;\n p.cursor.x = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.x));\n p.cursor.y = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.y));\n });\n });\n};\n\nconst moved = (point: Point, withCursor = true): { x: number; y: number } => {\n const x = point.x + point.wave.x + (withCursor ? point.cursor.x : 0);\n const y = point.y + point.wave.y + (withCursor ? point.cursor.y : 0);\n return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };\n};\n\nconst drawLines = () => {\n const { width, height } = bounding;\n if (!ctx) return;\n\n ctx.clearRect(0, 0, width, height);\n ctx.beginPath();\n ctx.strokeStyle = config.lineColor;\n\n lines.forEach(points => {\n let p1 = moved(points[0], false);\n ctx!.moveTo(p1.x, p1.y);\n points.forEach((p, idx) => {\n const isLast = idx === points.length - 1;\n p1 = moved(p, !isLast);\n const p2 = moved(points[idx + 1] || points[points.length - 1], !isLast);\n ctx!.lineTo(p1.x, p1.y);\n if (isLast) ctx!.moveTo(p2.x, p2.y);\n });\n });\n ctx.stroke();\n};\n\nconst tick = (t: number) => {\n const container = containerRef.value;\n if (!container) return;\n\n mouse.sx += (mouse.x - mouse.sx) * 0.1;\n mouse.sy += (mouse.y - mouse.sy) * 0.1;\n const dx = mouse.x - mouse.lx,\n dy = mouse.y - mouse.ly;\n const d = Math.hypot(dx, dy);\n mouse.v = d;\n mouse.vs += (d - mouse.vs) * 0.1;\n mouse.vs = Math.min(100, mouse.vs);\n mouse.lx = mouse.x;\n mouse.ly = mouse.y;\n mouse.a = Math.atan2(dy, dx);\n container.style.setProperty('--x', `${mouse.sx}px`);\n container.style.setProperty('--y', `${mouse.sy}px`);\n\n movePoints(t);\n drawLines();\n frameId = requestAnimationFrame(tick);\n};\n\nconst onResize = () => {\n setSize();\n setLines();\n};\n\nconst updateMouse = (x: number, y: number) => {\n mouse.x = x - bounding.left;\n mouse.y = y - bounding.top;\n if (!mouse.set) {\n mouse.sx = mouse.x;\n mouse.sy = mouse.y;\n mouse.lx = mouse.x;\n mouse.ly = mouse.y;\n mouse.set = true;\n }\n};\n\nconst onMouseMove = (e: MouseEvent) => {\n updateMouse(e.clientX, e.clientY);\n};\n\nconst onTouchMove = (e: TouchEvent) => {\n const touch = e.touches[0];\n updateMouse(touch.clientX, touch.clientY);\n};\n\nonMounted(() => {\n const canvas = canvasRef.value;\n const container = containerRef.value;\n if (!canvas || !container) return;\n\n ctx = canvas.getContext('2d');\n noise = new Noise(Math.random());\n\n setSize();\n setLines();\n frameId = requestAnimationFrame(tick);\n\n window.addEventListener('resize', onResize);\n window.addEventListener('mousemove', onMouseMove);\n window.addEventListener('touchmove', onTouchMove, { passive: false });\n});\n\nonUnmounted(() => {\n window.removeEventListener('resize', onResize);\n window.removeEventListener('mousemove', onMouseMove);\n window.removeEventListener('touchmove', onTouchMove);\n if (frameId !== null) {\n cancelAnimationFrame(frameId);\n }\n});\n\nwatch(\n () => [\n props.lineColor,\n props.waveSpeedX,\n props.waveSpeedY,\n props.waveAmpX,\n props.waveAmpY,\n props.friction,\n props.tension,\n props.maxCursorMove,\n props.xGap,\n props.yGap\n ],\n () => {\n config = {\n lineColor: props.lineColor,\n waveSpeedX: props.waveSpeedX,\n waveSpeedY: props.waveSpeedY,\n waveAmpX: props.waveAmpX,\n waveAmpY: props.waveAmpY,\n friction: props.friction,\n tension: props.tension,\n maxCursorMove: props.maxCursorMove,\n xGap: props.xGap,\n yGap: props.yGap\n };\n }\n);\n</script>\n","path":"Waves/Waves.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Backgrounds"]} |