mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
7.8 KiB
JSON
1 line
7.8 KiB
JSON
{"name":"ElectricBorder","title":"ElectricBorder","description":"Jittery electric energy border with animated arcs, glow and adjustable intensity.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, useTemplateRef, watch, type CSSProperties } from 'vue';\n\ntype ElectricBorderProps = {\n color?: string;\n speed?: number;\n chaos?: number;\n thickness?: number;\n className?: string;\n style?: CSSProperties;\n};\n\nconst props = withDefaults(defineProps<ElectricBorderProps>(), {\n color: '#28FF85',\n speed: 1,\n chaos: 1,\n thickness: 2\n});\n\nfunction hexToRgba(hex: string, alpha = 1): string {\n if (!hex) return `rgba(0,0,0,${alpha})`;\n let h = hex.replace('#', '');\n if (h.length === 3) {\n h = h\n .split('')\n .map(c => c + c)\n .join('');\n }\n const int = parseInt(h, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nconst rawId = `id-${crypto.randomUUID().replace(/[:]/g, '')}`;\nconst filterId = `turbulent-displace-${rawId}`;\n\nconst svgRef = useTemplateRef('svgRef');\nconst rootRef = useTemplateRef('rootRef');\nconst strokeRef = useTemplateRef('strokeRef');\n\nconst updateAnim = () => {\n const svg = svgRef.value;\n const host = rootRef.value;\n if (!svg || !host) return;\n\n if (strokeRef.value) {\n strokeRef.value.style.filter = `url(#${filterId})`;\n }\n\n const width = Math.max(1, Math.round(host.clientWidth || host.getBoundingClientRect().width || 0));\n const height = Math.max(1, Math.round(host.clientHeight || host.getBoundingClientRect().height || 0));\n\n const dyAnims = Array.from(svg.querySelectorAll('feOffset > animate[attributeName=\"dy\"]')) as SVGAnimateElement[];\n if (dyAnims.length >= 2) {\n dyAnims[0].setAttribute('values', `${height}; 0`);\n dyAnims[1].setAttribute('values', `0; -${height}`);\n }\n\n const dxAnims = Array.from(svg.querySelectorAll('feOffset > animate[attributeName=\"dx\"]')) as SVGAnimateElement[];\n if (dxAnims.length >= 2) {\n dxAnims[0].setAttribute('values', `${width}; 0`);\n dxAnims[1].setAttribute('values', `0; -${width}`);\n }\n\n const baseDur = 6;\n const dur = Math.max(0.001, baseDur / (props.speed || 1));\n [...dyAnims, ...dxAnims].forEach(a => a.setAttribute('dur', `${dur}s`));\n\n const disp = svg.querySelector('feDisplacementMap');\n if (disp) disp.setAttribute('scale', String(30 * (props.chaos || 1)));\n\n const filterEl = svg.querySelector<SVGFilterElement>(`#${CSS.escape(filterId)}`);\n if (filterEl) {\n filterEl.setAttribute('x', '-200%');\n filterEl.setAttribute('y', '-200%');\n filterEl.setAttribute('width', '500%');\n filterEl.setAttribute('height', '500%');\n }\n\n requestAnimationFrame(() => {\n [...dyAnims, ...dxAnims].forEach((a: SVGAnimateElement) => {\n if (typeof a.beginElement === 'function') {\n try {\n a.beginElement();\n } catch {}\n }\n });\n });\n};\n\nwatch(\n () => [props.speed, props.chaos],\n () => {\n updateAnim();\n },\n { deep: true }\n);\n\nlet ro: ResizeObserver | null = null;\n\nonMounted(() => {\n if (!rootRef.value) return;\n ro = new ResizeObserver(() => updateAnim());\n ro.observe(rootRef.value);\n updateAnim();\n});\n\nonBeforeUnmount(() => {\n if (ro) ro.disconnect();\n});\n\nconst inheritRadius = computed<CSSProperties>(() => {\n const radius = props.style?.borderRadius;\n\n if (radius === undefined) {\n return { borderRadius: 'inherit' };\n }\n\n if (typeof radius === 'number') {\n return { borderRadius: `${radius}px` };\n }\n\n return { borderRadius: radius };\n});\n\nconst strokeStyle = computed<CSSProperties>(() => ({\n ...inheritRadius.value,\n borderWidth: `${props.thickness}px`,\n borderStyle: 'solid',\n borderColor: props.color\n}));\n\nconst glow1Style = computed<CSSProperties>(() => ({\n ...inheritRadius.value,\n borderWidth: `${props.thickness}px`,\n borderStyle: 'solid',\n borderColor: hexToRgba(props.color, 0.6),\n filter: `blur(${0.5 + props.thickness * 0.25}px)`,\n opacity: 0.5\n}));\n\nconst glow2Style = computed<CSSProperties>(() => ({\n ...inheritRadius.value,\n borderWidth: `${props.thickness}px`,\n borderStyle: 'solid',\n borderColor: props.color,\n filter: `blur(${2 + props.thickness * 0.5}px)`,\n opacity: 0.5\n}));\n\nconst bgGlowStyle = computed<CSSProperties>(() => ({\n ...inheritRadius.value,\n transform: 'scale(1.08)',\n filter: 'blur(32px)',\n opacity: 0.3,\n zIndex: -1,\n background: `linear-gradient(-30deg, ${hexToRgba(props.color, 0.8)}, transparent, ${props.color})`\n}));\n</script>\n\n<template>\n <div ref=\"rootRef\" :class=\"['relative isolate', className]\" :style=\"style\">\n <svg\n ref=\"svgRef\"\n class=\"fixed opacity-0 w-0 h-0 pointer-events-none\"\n style=\"position: absolute; top: -9999px; left: -9999px\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <defs>\n <filter :id=\"filterId\" color-interpolation-filters=\"sRGB\" x=\"-200%\" y=\"-200%\" width=\"500%\" height=\"500%\">\n <feTurbulence type=\"turbulence\" baseFrequency=\"0.015\" numOctaves=\"8\" result=\"noise1\" seed=\"1\" />\n <feOffset in=\"noise1\" dx=\"0\" dy=\"0\" result=\"offsetNoise1\">\n <animate attributeName=\"dy\" values=\"500; 0\" dur=\"6s\" repeatCount=\"indefinite\" calcMode=\"linear\" />\n </feOffset>\n\n <feTurbulence type=\"turbulence\" baseFrequency=\"0.015\" numOctaves=\"8\" result=\"noise2\" seed=\"3\" />\n <feOffset in=\"noise2\" dx=\"0\" dy=\"0\" result=\"offsetNoise2\">\n <animate attributeName=\"dy\" values=\"0; -500\" dur=\"6s\" repeatCount=\"indefinite\" calcMode=\"linear\" />\n </feOffset>\n\n <feTurbulence type=\"turbulence\" baseFrequency=\"0.02\" numOctaves=\"6\" result=\"noise3\" seed=\"5\" />\n <feOffset in=\"noise3\" dx=\"0\" dy=\"0\" result=\"offsetNoise3\">\n <animate attributeName=\"dx\" values=\"500; 0\" dur=\"6s\" repeatCount=\"indefinite\" calcMode=\"linear\" />\n </feOffset>\n\n <feTurbulence type=\"turbulence\" baseFrequency=\"0.02\" numOctaves=\"6\" result=\"noise4\" seed=\"7\" />\n <feOffset in=\"noise4\" dx=\"0\" dy=\"0\" result=\"offsetNoise4\">\n <animate attributeName=\"dx\" values=\"0; -500\" dur=\"6s\" repeatCount=\"indefinite\" calcMode=\"linear\" />\n </feOffset>\n\n <feComposite in=\"offsetNoise1\" in2=\"offsetNoise2\" operator=\"add\" result=\"verticalNoise\" />\n <feComposite in=\"offsetNoise3\" in2=\"offsetNoise4\" operator=\"add\" result=\"horizontalNoise\" />\n <feBlend in=\"verticalNoise\" in2=\"horizontalNoise\" mode=\"screen\" result=\"combinedNoise\" />\n\n <feDisplacementMap\n in=\"SourceGraphic\"\n in2=\"combinedNoise\"\n scale=\"30\"\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n </filter>\n </defs>\n </svg>\n\n <div class=\"absolute inset-0 pointer-events-none\" :style=\"inheritRadius\">\n <div ref=\"strokeRef\" class=\"box-border absolute inset-0\" :style=\"strokeStyle\" />\n <div class=\"box-border absolute inset-0\" :style=\"glow1Style\" />\n <div class=\"box-border absolute inset-0\" :style=\"glow2Style\" />\n <div class=\"absolute inset-0\" :style=\"bgGlowStyle\" />\n </div>\n\n <div class=\"relative\" :style=\"inheritRadius\">\n <slot />\n </div>\n </div>\n</template>\n","path":"ElectricBorder/ElectricBorder.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[],"devDependencies":[],"categories":["Animations"]} |