mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
4.7 KiB
JSON
1 line
4.7 KiB
JSON
{"name":"FlowingMenu","title":"FlowingMenu","description":"Liquid flowing active indicator glides between menu items.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div class=\"w-full h-full overflow-hidden\">\n <nav class=\"flex flex-col h-full m-0 p-0\">\n <div\n v-for=\"(item, idx) in items\"\n :key=\"idx\"\n class=\"flex-1 relative overflow-hidden text-center shadow-[0_-1px_0_0_#fff]\"\n :ref=\"el => setItemRef(el as HTMLDivElement, idx)\"\n >\n <a\n class=\"flex items-center justify-center h-full relative cursor-pointer uppercase no-underline font-semibold text-white text-[4vh] hover:text-[#0b0b0b] focus:text-white focus-visible:text-[#0b0b0b]\"\n :href=\"item.link\"\n @mouseenter=\"ev => handleMouseEnter(ev, idx)\"\n @mouseleave=\"ev => handleMouseLeave(ev, idx)\"\n >\n {{ item.text }}\n </a>\n\n <div\n class=\"absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none bg-white translate-y-[101%]\"\n :ref=\"el => (marqueeRefs[idx] = el as HTMLDivElement)\"\n >\n <div class=\"h-full w-[200%] flex\" :ref=\"el => (marqueeInnerRefs[idx] = el as HTMLDivElement)\">\n <div class=\"flex items-center relative h-full w-[200%] will-change-transform animate-marquee\">\n <template v-for=\"i in 4\" :key=\"`${idx}-${i}`\">\n <span class=\"text-[#0b0b0b] uppercase font-normal text-[4vh] leading-[1.2] p-[1vh_1vw_0]\">\n {{ item.text }}\n </span>\n\n <div\n class=\"w-[200px] h-[7vh] my-[2em] mx-[2vw] p-[1em_0] rounded-[50px] bg-cover bg-center\"\n :style=\"{ backgroundImage: `url(${item.image})` }\"\n />\n </template>\n </div>\n </div>\n </div>\n </div>\n </nav>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { gsap } from 'gsap';\n\ninterface MenuItemProps {\n link: string;\n text: string;\n image: string;\n}\n\ninterface Props {\n items?: MenuItemProps[];\n}\n\nwithDefaults(defineProps<Props>(), {\n items: () => []\n});\n\nconst itemRefs = ref<(HTMLDivElement | null)[]>([]);\nconst marqueeRefs = ref<(HTMLDivElement | null)[]>([]);\nconst marqueeInnerRefs = ref<(HTMLDivElement | null)[]>([]);\n\nconst animationDefaults = { duration: 0.6, ease: 'expo' };\n\nconst setItemRef = (el: HTMLDivElement | null, idx: number) => {\n if (el) {\n itemRefs.value[idx] = el;\n }\n};\n\nconst findClosestEdge = (mouseX: number, mouseY: number, width: number, height: number): 'top' | 'bottom' => {\n const topEdgeDist = Math.pow(mouseX - width / 2, 2) + Math.pow(mouseY, 2);\n const bottomEdgeDist = Math.pow(mouseX - width / 2, 2) + Math.pow(mouseY - height, 2);\n return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom';\n};\n\nconst handleMouseEnter = (ev: MouseEvent, idx: number) => {\n const itemRef = itemRefs.value[idx];\n const marqueeRef = marqueeRefs.value[idx];\n const marqueeInnerRef = marqueeInnerRefs.value[idx];\n\n if (!itemRef || !marqueeRef || !marqueeInnerRef) return;\n\n const rect = itemRef.getBoundingClientRect();\n const edge = findClosestEdge(ev.clientX - rect.left, ev.clientY - rect.top, rect.width, rect.height);\n\n const tl = gsap.timeline({ defaults: animationDefaults });\n tl.set(marqueeRef, { y: edge === 'top' ? '-101%' : '101%' })\n .set(marqueeInnerRef, { y: edge === 'top' ? '101%' : '-101%' })\n .to([marqueeRef, marqueeInnerRef], { y: '0%' });\n};\n\nconst handleMouseLeave = (ev: MouseEvent, idx: number) => {\n const itemRef = itemRefs.value[idx];\n const marqueeRef = marqueeRefs.value[idx];\n const marqueeInnerRef = marqueeInnerRefs.value[idx];\n\n if (!itemRef || !marqueeRef || !marqueeInnerRef) return;\n\n const rect = itemRef.getBoundingClientRect();\n const edge = findClosestEdge(ev.clientX - rect.left, ev.clientY - rect.top, rect.width, rect.height);\n\n const tl = gsap.timeline({ defaults: animationDefaults });\n tl.to(marqueeRef, { y: edge === 'top' ? '-101%' : '101%' }).to(marqueeInnerRef, {\n y: edge === 'top' ? '101%' : '-101%'\n });\n};\n</script>\n\n<style scoped>\n@keyframes marquee {\n from {\n transform: translateX(0%);\n }\n\n to {\n transform: translateX(-50%);\n }\n}\n\n.animate-marquee {\n animation: marquee 15s linear infinite;\n}\n</style>\n","path":"FlowingMenu/FlowingMenu.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"gsap","version":"^3.13.0"}],"devDependencies":[],"categories":["Components"]} |