mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
8.0 KiB
JSON
1 line
8.0 KiB
JSON
{"name":"LetterGlitch","title":"LetterGlitch","description":"Matrix style letter animation.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div class=\"relative overflow-hidden\">\n <canvas ref=\"canvasRef\" class=\"top-0 left-0 absolute w-full h-full\" />\n\n <div\n v-if=\"outerVignette\"\n class=\"top-0 left-0 absolute bg-[radial-gradient(circle,rgba(0,0,0,0)_60%,rgba(0,0,0,1)_100%)] w-full h-full pointer-events-none\"\n />\n\n <div\n v-if=\"centerVignette\"\n class=\"top-0 left-0 absolute bg-[radial-gradient(circle,rgba(0,0,0,0.8)_0%,rgba(0,0,0,0)_60%)] w-full h-full pointer-events-none\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\n\ninterface Props {\n glitchColors?: string[];\n glitchSpeed?: number;\n centerVignette?: boolean;\n outerVignette?: boolean;\n smooth?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n glitchColors: () => ['#2b4539', '#61dca3', '#61b3dc'],\n glitchSpeed: 50,\n centerVignette: false,\n outerVignette: false,\n smooth: true\n});\n\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\nconst animationRef = ref<number | null>(null);\nconst letters = ref<\n {\n char: string;\n color: string;\n targetColor: string;\n colorProgress: number;\n }[]\n>([]);\nconst grid = ref({ columns: 0, rows: 0 });\nconst context = ref<CanvasRenderingContext2D | null>(null);\nconst lastGlitchTime = ref(Date.now());\n\nconst fontSize = 16;\nconst charWidth = 10;\nconst charHeight = 20;\n\nconst lettersAndSymbols = [\n 'A',\n 'B',\n 'C',\n 'D',\n 'E',\n 'F',\n 'G',\n 'H',\n 'I',\n 'J',\n 'K',\n 'L',\n 'M',\n 'N',\n 'O',\n 'P',\n 'Q',\n 'R',\n 'S',\n 'T',\n 'U',\n 'V',\n 'W',\n 'X',\n 'Y',\n 'Z',\n '!',\n '@',\n '#',\n '$',\n '&',\n '*',\n '(',\n ')',\n '-',\n '_',\n '+',\n '=',\n '/',\n '[',\n ']',\n '{',\n '}',\n ';',\n ':',\n '<',\n '>',\n ',',\n '0',\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9'\n];\n\nconst getRandomChar = () => {\n return lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)];\n};\n\nconst getRandomColor = () => {\n return props.glitchColors[Math.floor(Math.random() * props.glitchColors.length)];\n};\n\nconst hexToRgb = (hex: string) => {\n const shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\n hex = hex.replace(shorthandRegex, (m, r, g, b) => {\n return r + r + g + g + b + b;\n });\n\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? {\n r: parseInt(result[1], 16),\n g: parseInt(result[2], 16),\n b: parseInt(result[3], 16)\n }\n : null;\n};\n\nconst interpolateColor = (\n start: { r: number; g: number; b: number },\n end: { r: number; g: number; b: number },\n factor: number\n) => {\n const result = {\n r: Math.round(start.r + (end.r - start.r) * factor),\n g: Math.round(start.g + (end.g - start.g) * factor),\n b: Math.round(start.b + (end.b - start.b) * factor)\n };\n return `rgb(${result.r}, ${result.g}, ${result.b})`;\n};\n\nconst calculateGrid = (width: number, height: number) => {\n const columns = Math.ceil(width / charWidth);\n const rows = Math.ceil(height / charHeight);\n return { columns, rows };\n};\n\nconst initializeLetters = (columns: number, rows: number) => {\n grid.value = { columns, rows };\n const totalLetters = columns * rows;\n letters.value = Array.from({ length: totalLetters }, () => ({\n char: getRandomChar(),\n color: getRandomColor(),\n targetColor: getRandomColor(),\n colorProgress: 1\n }));\n};\n\nconst resizeCanvas = () => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n const parent = canvas.parentElement;\n if (!parent) return;\n\n const dpr = window.devicePixelRatio || 1;\n\n const parentWidth = parent.parentElement?.offsetWidth || parent.offsetWidth || window.innerWidth;\n const parentHeight = parent.parentElement?.offsetHeight || parent.offsetHeight || window.innerHeight;\n\n const width = Math.max(parentWidth, 300);\n const height = Math.max(parentHeight, 300);\n\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n if (context.value) {\n context.value.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n const { columns, rows } = calculateGrid(width, height);\n initializeLetters(columns, rows);\n drawLetters();\n};\n\nconst drawLetters = () => {\n if (!context.value || letters.value.length === 0) return;\n const ctx = context.value;\n const { width, height } = canvasRef.value!.getBoundingClientRect();\n ctx.clearRect(0, 0, width, height);\n ctx.font = `${fontSize}px monospace`;\n ctx.textBaseline = 'top';\n\n letters.value.forEach((letter, index) => {\n const x = (index % grid.value.columns) * charWidth;\n const y = Math.floor(index / grid.value.columns) * charHeight;\n ctx.fillStyle = letter.color;\n ctx.fillText(letter.char, x, y);\n });\n};\n\nconst updateLetters = () => {\n if (!letters.value || letters.value.length === 0) return;\n\n const updateCount = Math.max(1, Math.floor(letters.value.length * 0.05));\n\n for (let i = 0; i < updateCount; i++) {\n const index = Math.floor(Math.random() * letters.value.length);\n if (!letters.value[index]) continue;\n\n letters.value[index].char = getRandomChar();\n letters.value[index].targetColor = getRandomColor();\n\n if (!props.smooth) {\n letters.value[index].color = letters.value[index].targetColor;\n letters.value[index].colorProgress = 1;\n } else {\n letters.value[index].colorProgress = 0;\n }\n }\n};\n\nconst handleSmoothTransitions = () => {\n let needsRedraw = false;\n letters.value.forEach(letter => {\n if (letter.colorProgress < 1) {\n letter.colorProgress += 0.05;\n if (letter.colorProgress > 1) letter.colorProgress = 1;\n\n const startRgb = hexToRgb(letter.color);\n const endRgb = hexToRgb(letter.targetColor);\n if (startRgb && endRgb) {\n letter.color = interpolateColor(startRgb, endRgb, letter.colorProgress);\n needsRedraw = true;\n }\n }\n });\n\n if (needsRedraw) {\n drawLetters();\n }\n};\n\nconst animate = () => {\n const now = Date.now();\n if (now - lastGlitchTime.value >= props.glitchSpeed) {\n updateLetters();\n drawLetters();\n lastGlitchTime.value = now;\n }\n\n if (props.smooth) {\n handleSmoothTransitions();\n }\n\n animationRef.value = requestAnimationFrame(animate);\n};\n\nlet resizeTimeout: ReturnType<typeof setTimeout>;\n\nconst handleResize = () => {\n clearTimeout(resizeTimeout);\n resizeTimeout = setTimeout(() => {\n if (animationRef.value) {\n cancelAnimationFrame(animationRef.value);\n }\n resizeCanvas();\n animate();\n }, 100);\n};\n\nonMounted(() => {\n const canvas = canvasRef.value;\n if (!canvas) return;\n\n context.value = canvas.getContext('2d');\n resizeCanvas();\n animate();\n\n window.addEventListener('resize', handleResize);\n});\n\nonUnmounted(() => {\n if (animationRef.value) {\n cancelAnimationFrame(animationRef.value);\n }\n window.removeEventListener('resize', handleResize);\n});\n\nwatch([() => props.glitchSpeed, () => props.smooth], () => {\n if (animationRef.value) {\n cancelAnimationFrame(animationRef.value);\n }\n animate();\n});\n</script>\n\n<style scoped>\ndiv {\n position: absolute !important;\n top: 0 !important;\n left: 0 !important;\n width: 100% !important;\n height: 100% !important;\n}\n\n:deep(canvas) {\n position: absolute !important;\n top: 0 !important;\n left: 0 !important;\n width: 100% !important;\n height: 100% !important;\n}\n</style>\n","path":"LetterGlitch/LetterGlitch.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Backgrounds"]} |