mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
1 line
5.2 KiB
JSON
1 line
5.2 KiB
JSON
{"name":"DecayCard","title":"DecayCard","description":"Hover parallax effect that disintegrates the content of a card.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div ref=\"svgRef\" class=\"relative\" :style=\"{ width: `${width}px`, height: `${height}px` }\">\n <svg\n viewBox=\"-60 -75 720 900\"\n preserveAspectRatio=\"xMidYMid slice\"\n class=\"relative w-full h-full block [will-change:transform]\"\n >\n <filter id=\"imgFilter\">\n <feTurbulence\n type=\"turbulence\"\n baseFrequency=\"0.015\"\n numOctaves=\"5\"\n seed=\"4\"\n stitchTiles=\"stitch\"\n x=\"0%\"\n y=\"0%\"\n width=\"100%\"\n height=\"100%\"\n result=\"turbulence1\"\n />\n\n <feDisplacementMap\n ref=\"displacementMapRef\"\n in=\"SourceGraphic\"\n in2=\"turbulence1\"\n scale=\"0\"\n xChannelSelector=\"R\"\n yChannelSelector=\"B\"\n x=\"0%\"\n y=\"0%\"\n width=\"100%\"\n height=\"100%\"\n result=\"displacementMap3\"\n />\n </filter>\n\n <g>\n <image\n :href=\"image\"\n x=\"0\"\n y=\"0\"\n width=\"600\"\n height=\"750\"\n filter=\"url(#imgFilter)\"\n preserveAspectRatio=\"xMidYMid slice\"\n />\n </g>\n </svg>\n\n <div\n class=\"absolute bottom-[1.2em] left-[1em] tracking-[-0.5px] font-black text-[2rem] leading-[1.5em] first-line:text-[4rem]\"\n >\n <slot />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, useTemplateRef } from 'vue';\nimport { gsap } from 'gsap';\n\ninterface Props {\n width?: number;\n height?: number;\n image?: string;\n}\n\nwithDefaults(defineProps<Props>(), {\n width: 300,\n height: 400,\n image: 'https://picsum.photos/300/400?grayscale'\n});\n\nconst svgRef = useTemplateRef<HTMLDivElement>('svgRef');\nconst displacementMapRef = ref<SVGFEDisplacementMapElement | null>(null);\n\nlet cursor = {\n x: typeof window !== 'undefined' ? window.innerWidth / 2 : 0,\n y: typeof window !== 'undefined' ? window.innerHeight / 2 : 0\n};\n\nlet cachedCursor = { ...cursor };\n\nlet winsize = {\n width: typeof window !== 'undefined' ? window.innerWidth : 0,\n height: typeof window !== 'undefined' ? window.innerHeight : 0\n};\n\nlet animationFrameId: number | null = null;\n\nconst lerp = (a: number, b: number, n: number): number => (1 - n) * a + n * b;\n\nconst map = (x: number, a: number, b: number, c: number, d: number): number => ((x - a) * (d - c)) / (b - a) + c;\n\nconst distance = (x1: number, x2: number, y1: number, y2: number): number => Math.hypot(x1 - x2, y1 - y2);\n\nconst handleResize = (): void => {\n winsize = {\n width: window.innerWidth,\n height: window.innerHeight\n };\n};\n\nconst handleMouseMove = (ev: MouseEvent): void => {\n cursor = { x: ev.clientX, y: ev.clientY };\n};\n\nconst imgValues = {\n imgTransforms: { x: 0, y: 0, rz: 0 },\n displacementScale: 0\n};\n\nconst render = () => {\n let targetX = lerp(imgValues.imgTransforms.x, map(cursor.x, 0, winsize.width, -120, 120), 0.1);\n let targetY = lerp(imgValues.imgTransforms.y, map(cursor.y, 0, winsize.height, -120, 120), 0.1);\n const targetRz = lerp(imgValues.imgTransforms.rz, map(cursor.x, 0, winsize.width, -10, 10), 0.1);\n\n const bound = 50;\n if (targetX > bound) targetX = bound + (targetX - bound) * 0.2;\n if (targetX < -bound) targetX = -bound + (targetX + bound) * 0.2;\n if (targetY > bound) targetY = bound + (targetY - bound) * 0.2;\n if (targetY < -bound) targetY = -bound + (targetY + bound) * 0.2;\n\n imgValues.imgTransforms.x = targetX;\n imgValues.imgTransforms.y = targetY;\n imgValues.imgTransforms.rz = targetRz;\n\n if (svgRef.value) {\n gsap.set(svgRef.value, {\n x: imgValues.imgTransforms.x,\n y: imgValues.imgTransforms.y,\n rotateZ: imgValues.imgTransforms.rz\n });\n }\n\n const cursorTravelledDistance = distance(cachedCursor.x, cursor.x, cachedCursor.y, cursor.y);\n imgValues.displacementScale = lerp(imgValues.displacementScale, map(cursorTravelledDistance, 0, 200, 0, 400), 0.06);\n\n if (displacementMapRef.value) {\n gsap.set(displacementMapRef.value, {\n attr: { scale: imgValues.displacementScale }\n });\n }\n\n cachedCursor = { ...cursor };\n\n animationFrameId = requestAnimationFrame(render);\n};\n\nonMounted(() => {\n if (typeof window !== 'undefined') {\n window.addEventListener('resize', handleResize);\n window.addEventListener('mousemove', handleMouseMove);\n render();\n }\n});\n\nonUnmounted(() => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('resize', handleResize);\n window.removeEventListener('mousemove', handleMouseMove);\n }\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n});\n</script>\n","path":"DecayCard/DecayCard.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Components"]} |