mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
4.9 KiB
JSON
1 line
4.9 KiB
JSON
{"name":"ClickSpark","title":"ClickSpark","description":"Creates particle spark bursts at click position.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div ref=\"containerRef\" class=\"relative w-full h-full\" @click=\"handleClick\">\n <canvas ref=\"canvasRef\" class=\"absolute inset-0 pointer-events-none\" />\n\n <slot />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, computed, watch, useTemplateRef } from 'vue';\n\ninterface Spark {\n x: number;\n y: number;\n angle: number;\n startTime: number;\n}\n\ninterface Props {\n sparkColor?: string;\n sparkSize?: number;\n sparkRadius?: number;\n sparkCount?: number;\n duration?: number;\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';\n extraScale?: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n sparkColor: '#fff',\n sparkSize: 10,\n sparkRadius: 15,\n sparkCount: 8,\n duration: 400,\n easing: 'ease-out',\n extraScale: 1.0\n});\n\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef');\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\nconst sparks = ref<Spark[]>([]);\nconst startTimeRef = ref<number | null>(null);\nconst animationId = ref<number | null>(null);\n\nconst easeFunc = computed(() => {\n return (t: number) => {\n switch (props.easing) {\n case 'linear':\n return t;\n case 'ease-in':\n return t * t;\n case 'ease-in-out':\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n default:\n return t * (2 - t);\n }\n };\n});\n\nconst handleClick = (e: MouseEvent) => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n const rect = canvas.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n\n const now = performance.now();\n const newSparks: Spark[] = Array.from({ length: props.sparkCount }, (_, i) => ({\n x,\n y,\n angle: (2 * Math.PI * i) / props.sparkCount,\n startTime: now\n }));\n\n sparks.value.push(...newSparks);\n};\n\nconst draw = (timestamp: number) => {\n if (!startTimeRef.value) {\n startTimeRef.value = timestamp;\n }\n\n const canvas = canvasRef.value;\n const ctx = canvas?.getContext('2d');\n if (!ctx || !canvas) return;\n\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n sparks.value = sparks.value.filter((spark: Spark) => {\n const elapsed = timestamp - spark.startTime;\n if (elapsed >= props.duration) {\n return false;\n }\n\n const progress = elapsed / props.duration;\n const eased = easeFunc.value(progress);\n\n const distance = eased * props.sparkRadius * props.extraScale;\n const lineLength = props.sparkSize * (1 - eased);\n\n const x1 = spark.x + distance * Math.cos(spark.angle);\n const y1 = spark.y + distance * Math.sin(spark.angle);\n const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);\n const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);\n\n ctx.strokeStyle = props.sparkColor;\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n\n return true;\n });\n\n animationId.value = requestAnimationFrame(draw);\n};\n\nconst resizeCanvas = () => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n const parent = canvas.parentElement;\n if (!parent) return;\n\n const { width, height } = parent.getBoundingClientRect();\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width;\n canvas.height = height;\n }\n};\n\nlet resizeTimeout: ReturnType<typeof setTimeout>;\n\nconst handleResize = () => {\n clearTimeout(resizeTimeout);\n resizeTimeout = setTimeout(resizeCanvas, 100);\n};\n\nlet resizeObserver: ResizeObserver | null = null;\n\nonMounted(() => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n const parent = canvas.parentElement;\n if (!parent) return;\n\n resizeObserver = new ResizeObserver(handleResize);\n resizeObserver.observe(parent);\n\n resizeCanvas();\n\n animationId.value = requestAnimationFrame(draw);\n});\n\nonUnmounted(() => {\n if (resizeObserver) {\n resizeObserver.disconnect();\n }\n clearTimeout(resizeTimeout);\n\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n }\n});\n\nwatch(\n [\n () => props.sparkColor,\n () => props.sparkSize,\n () => props.sparkRadius,\n () => props.sparkCount,\n () => props.duration,\n easeFunc,\n () => props.extraScale\n ],\n () => {\n if (animationId.value) {\n cancelAnimationFrame(animationId.value);\n }\n animationId.value = requestAnimationFrame(draw);\n }\n);\n</script>\n","path":"ClickSpark/ClickSpark.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]} |