mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 22:49:31 -07:00
1 line
6.2 KiB
JSON
1 line
6.2 KiB
JSON
{"name":"Squares","title":"Squares","description":"Animated squares with scaling + direction customization.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <canvas ref=\"canvasRef\" class=\"w-full h-full border-none block\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\n\ntype CanvasStrokeStyle = string | CanvasGradient | CanvasPattern;\n\ninterface GridOffset {\n x: number;\n y: number;\n}\n\ninterface Props {\n direction?: 'diagonal' | 'up' | 'right' | 'down' | 'left';\n speed?: number;\n borderColor?: CanvasStrokeStyle;\n squareSize?: number;\n hoverFillColor?: CanvasStrokeStyle;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n direction: 'right',\n speed: 1,\n borderColor: '#999',\n squareSize: 40,\n hoverFillColor: '#222'\n});\n\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\nconst requestRef = ref<number | null>(null);\nconst numSquaresX = ref<number>(0);\nconst numSquaresY = ref<number>(0);\nconst gridOffset = ref<GridOffset>({ x: 0, y: 0 });\nconst hoveredSquareRef = ref<GridOffset | null>(null);\n\nlet ctx: CanvasRenderingContext2D | null = null;\n\nconst resizeCanvas = () => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n numSquaresX.value = Math.ceil(canvas.width / props.squareSize) + 1;\n numSquaresY.value = Math.ceil(canvas.height / props.squareSize) + 1;\n};\n\nconst drawGrid = () => {\n const canvas = canvasRef.value;\n if (!ctx || !canvas) return;\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n const startX = Math.floor(gridOffset.value.x / props.squareSize) * props.squareSize;\n const startY = Math.floor(gridOffset.value.y / props.squareSize) * props.squareSize;\n\n for (let x = startX; x < canvas.width + props.squareSize; x += props.squareSize) {\n for (let y = startY; y < canvas.height + props.squareSize; y += props.squareSize) {\n const squareX = x - (gridOffset.value.x % props.squareSize);\n const squareY = y - (gridOffset.value.y % props.squareSize);\n\n if (\n hoveredSquareRef.value &&\n Math.floor((x - startX) / props.squareSize) === hoveredSquareRef.value.x &&\n Math.floor((y - startY) / props.squareSize) === hoveredSquareRef.value.y\n ) {\n ctx.fillStyle = props.hoverFillColor;\n ctx.fillRect(squareX, squareY, props.squareSize, props.squareSize);\n }\n\n ctx.strokeStyle = props.borderColor;\n ctx.strokeRect(squareX, squareY, props.squareSize, props.squareSize);\n }\n }\n\n const gradient = ctx.createRadialGradient(\n canvas.width / 2,\n canvas.height / 2,\n 0,\n canvas.width / 2,\n canvas.height / 2,\n Math.sqrt(canvas.width ** 2 + canvas.height ** 2) / 2\n );\n gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');\n gradient.addColorStop(1, '#0b0b0b');\n\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n};\n\nconst updateAnimation = () => {\n const effectiveSpeed = Math.max(props.speed, 0.1);\n\n switch (props.direction) {\n case 'right':\n gridOffset.value.x = (gridOffset.value.x - effectiveSpeed + props.squareSize) % props.squareSize;\n break;\n case 'left':\n gridOffset.value.x = (gridOffset.value.x + effectiveSpeed + props.squareSize) % props.squareSize;\n break;\n case 'up':\n gridOffset.value.y = (gridOffset.value.y + effectiveSpeed + props.squareSize) % props.squareSize;\n break;\n case 'down':\n gridOffset.value.y = (gridOffset.value.y - effectiveSpeed + props.squareSize) % props.squareSize;\n break;\n case 'diagonal':\n gridOffset.value.x = (gridOffset.value.x - effectiveSpeed + props.squareSize) % props.squareSize;\n gridOffset.value.y = (gridOffset.value.y - effectiveSpeed + props.squareSize) % props.squareSize;\n break;\n default:\n break;\n }\n\n drawGrid();\n requestRef.value = requestAnimationFrame(updateAnimation);\n};\n\nconst handleMouseMove = (event: MouseEvent) => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n const rect = canvas.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const mouseY = event.clientY - rect.top;\n\n const startX = Math.floor(gridOffset.value.x / props.squareSize) * props.squareSize;\n const startY = Math.floor(gridOffset.value.y / props.squareSize) * props.squareSize;\n\n const hoveredSquareX = Math.floor((mouseX + gridOffset.value.x - startX) / props.squareSize);\n const hoveredSquareY = Math.floor((mouseY + gridOffset.value.y - startY) / props.squareSize);\n\n if (\n !hoveredSquareRef.value ||\n hoveredSquareRef.value.x !== hoveredSquareX ||\n hoveredSquareRef.value.y !== hoveredSquareY\n ) {\n hoveredSquareRef.value = { x: hoveredSquareX, y: hoveredSquareY };\n }\n};\n\nconst handleMouseLeave = () => {\n hoveredSquareRef.value = null;\n};\n\nconst initializeCanvas = () => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n ctx = canvas.getContext('2d');\n resizeCanvas();\n\n canvas.addEventListener('mousemove', handleMouseMove);\n canvas.addEventListener('mouseleave', handleMouseLeave);\n window.addEventListener('resize', resizeCanvas);\n\n requestRef.value = requestAnimationFrame(updateAnimation);\n};\n\nconst cleanup = () => {\n const canvas = canvasRef.value;\n\n if (requestRef.value) {\n cancelAnimationFrame(requestRef.value);\n requestRef.value = null;\n }\n\n if (canvas) {\n canvas.removeEventListener('mousemove', handleMouseMove);\n canvas.removeEventListener('mouseleave', handleMouseLeave);\n }\n\n window.removeEventListener('resize', resizeCanvas);\n};\n\nonMounted(() => {\n initializeCanvas();\n});\n\nonUnmounted(() => {\n cleanup();\n});\n\nwatch(\n [\n () => props.direction,\n () => props.speed,\n () => props.borderColor,\n () => props.hoverFillColor,\n () => props.squareSize\n ],\n () => {\n cleanup();\n initializeCanvas();\n }\n);\n</script>\n","path":"Squares/Squares.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Backgrounds"]} |