feat(gallery): intro animation

This commit is contained in:
2026-01-03 15:58:19 +01:00
parent 21b2c6b38a
commit 8d1c87ef94

View File

@@ -2,6 +2,7 @@
import { LazyGalleryModal } from "#components"; import { LazyGalleryModal } from "#components";
import type { InternalApi } from "nitropack/types"; import type { InternalApi } from "nitropack/types";
import { useElementSize } from "@vueuse/core"; import { useElementSize } from "@vueuse/core";
import gsap from "gsap";
const { data: images } = await useAsyncData( const { data: images } = await useAsyncData(
"gallery", "gallery",
@@ -49,6 +50,44 @@ const openModal = async (index: number) => {
const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => { const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => {
return (image.height / image.width) * 100; return (image.height / image.width) * 100;
}; };
const isAnimating = ref(true);
onMounted(async () => {
await nextTick();
const scrollEl = scrollArea.value?.$el;
if (!scrollEl) return;
const preventScroll = (e: Event) => {
e.preventDefault();
e.stopPropagation();
};
scrollEl.addEventListener("wheel", preventScroll, { passive: false });
scrollEl.addEventListener("touchmove", preventScroll, { passive: false });
const items = document.querySelectorAll(".gallery-item");
gsap.fromTo(
items,
{ opacity: 0 },
{
opacity: 1,
duration: 0.6,
stagger: (index) => {
const line = Math.floor(index / lanes.value);
const column = index % lanes.value;
return line * 0.1 + column * 0.05;
},
ease: "power2.out",
onComplete: () => {
isAnimating.value = false;
scrollEl.removeEventListener("wheel", preventScroll);
scrollEl.removeEventListener("touchmove", preventScroll);
},
},
);
});
</script> </script>
<template> <template>
@@ -61,6 +100,7 @@ const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => {
estimateSize: 510, estimateSize: 510,
}" }"
class="p-6 lg:p-8 xl:p-10 2xl:p-12 h-screen bg-[#0a0a0a] text-white font-[JetBrains_Mono]" class="p-6 lg:p-8 xl:p-10 2xl:p-12 h-screen bg-[#0a0a0a] text-white font-[JetBrains_Mono]"
:class="{ 'no-scroll': isAnimating }"
> >
<template #default="{ item: image, index }"> <template #default="{ item: image, index }">
<header v-if="index === 0" class="pr-2 pb-6"> <header v-if="index === 0" class="pr-2 pb-6">
@@ -69,6 +109,7 @@ const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => {
size="md" size="md"
color="neutral" color="neutral"
variant="link" variant="link"
to="/"
:ui="{ :ui="{
base: 'px-0 text-neutral-600 hover:text-neutral-400', base: 'px-0 text-neutral-600 hover:text-neutral-400',
leadingIcon: 'size-4', leadingIcon: 'size-4',
@@ -85,7 +126,7 @@ const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => {
<div <div
v-else v-else
class="relative overflow-hidden rounded-sm cursor-pointer" class="gallery-item relative overflow-hidden rounded-sm cursor-pointer"
:style="{ paddingBottom: `${getAspectRatio(image)}%` }" :style="{ paddingBottom: `${getAspectRatio(image)}%` }"
@click="openModal(index)" @click="openModal(index)"
> >
@@ -115,3 +156,9 @@ const getAspectRatio = (image: InternalApi["/api/gallery"]["get"][number]) => {
</template> </template>
</UScrollArea> </UScrollArea>
</template> </template>
<style scoped>
.no-scroll :deep(*) {
pointer-events: none !important;
}
</style>