Refactor GridMotion component and demo for improved item handling and styling

This commit is contained in:
David Haz
2025-07-12 14:07:57 +03:00
parent 230f44f6e0
commit 0359d8043c
3 changed files with 36 additions and 75 deletions

View File

@@ -6,40 +6,16 @@ export const gridMotion: CodeObject = {
installation: `npm i gsap`, installation: `npm i gsap`,
usage: `<template> usage: `<template>
<GridMotion <GridMotion
:items="items" :items="images"
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import GridMotion from "./GridMotion.vue"; import GridMotion from "./GridMotion.vue";
const items = [ const imageUrl = 'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D';
"Item 1", const numberOfImages = 30;
`<div key='item-1'>Custom Content</div>`, const images = Array.from({ length: numberOfImages }, () => imageUrl);
"https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"Item 2",
`<div key='item-1'>Custom Content</div>`,
"Item 4",
`<div key='item-1'>Custom Content</div>`,
"https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"Item 5",
`<div key='item-1'>Custom Content</div>`,
"Item 7",
`<div key='item-1'>Custom Content</div>`,
"https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"Item 8",
`<div key='item-1'>Custom Content</div>`,
"Item 10",
`<div key='item-1'>Custom Content</div>`,
"https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"Item 11",
`<div key='item-1'>Custom Content</div>`,
"Item 13",
`<div key='item-1'>Custom Content</div>`,
"https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"Item 14",
// Add more items as needed
];
</script>`, </script>`,
code, code,
}; };

View File

@@ -9,16 +9,25 @@ interface GridMotionProps {
const props = withDefaults(defineProps<GridMotionProps>(), { const props = withDefaults(defineProps<GridMotionProps>(), {
items: () => [], items: () => [],
gradientColor: 'black' gradientColor: '#222222'
}); });
const gridRef = ref<HTMLElement | null>(null); const gridRef = ref<HTMLElement | null>(null);
const rowRefs = ref<HTMLElement[]>([]); const rowRefs = ref<HTMLElement[]>([]);
const mouseX = ref(window.innerWidth / 2); const mouseX = ref(window.innerWidth / 2);
const totalItems = 28; const totalItems = 70;
const defaultItems = Array.from({ length: totalItems }, (_, i) => `Item ${i + 1}`); const combinedItems = computed(() => {
const combinedItems = computed(() => (props.items.length > 0 ? props.items.slice(0, totalItems) : defaultItems)); if (props.items.length === 0) {
return [];
}
const repeatedItems = [];
for (let i = 0; i < totalItems; i++) {
repeatedItems.push(props.items[i % props.items.length]);
}
return repeatedItems;
});
function isImage(item: string) { function isImage(item: string) {
return typeof item === 'string' && item.startsWith('http'); return typeof item === 'string' && item.startsWith('http');
@@ -68,22 +77,26 @@ onMounted(() => {
<section <section
class="relative flex justify-center items-center w-full h-screen overflow-hidden" class="relative flex justify-center items-center w-full h-screen overflow-hidden"
:style="{ :style="{
background: `radial-gradient(circle, ${gradientColor} 0%, transparent 100%)` background: `radial-gradient(circle, ${gradientColor} 0%, transparent 100%)`,
backgroundPosition: 'center'
}" }"
> >
<div class="z-[4] absolute inset-0 bg-[length:250px] pointer-events-none"></div> <div class="z-[4] absolute inset-0 bg-[length:250px] pointer-events-none"></div>
<div <div class="z-[2] relative flex-none gap-4 grid grid-cols-1 w-[150vw] h-[150vh] rotate-[-15deg] origin-center">
class="z-[2] relative flex-none gap-4 grid grid-cols-1 grid-rows-4 w-[150vw] h-[150vh] rotate-[-15deg] origin-center"
>
<div <div
v-for="rowIndex in 4" v-for="rowIndex in 10"
:key="rowIndex" :key="rowIndex"
class="gap-4 grid grid-cols-7" class="gap-4 grid grid-cols-7"
:style="{ willChange: 'transform, filter' }" :style="{ willChange: 'transform, filter' }"
ref="rowRefs" ref="rowRefs"
> >
<div v-for="itemIndex in 7" :key="itemIndex" class="relative"> <div
v-for="itemIndex in 7"
:key="itemIndex"
class="relative h-[200px]"
v-show="combinedItems[(rowIndex - 1) * 7 + (itemIndex - 1)]"
>
<div <div
class="relative flex justify-center items-center bg-[#111] rounded-[10px] w-full h-full overflow-hidden text-[1.5rem] text-white" class="relative flex justify-center items-center bg-[#111] rounded-[10px] w-full h-full overflow-hidden text-[1.5rem] text-white"
> >

View File

@@ -1,18 +1,10 @@
<template> <template>
<TabbedLayout> <TabbedLayout>
<template #preview> <template #preview>
<h2 className="demo-title-extra">Images</h2> <div class="demo-container h-[600px]">
<div class="relative py-6 rounded-3xl overflow-hidden demo-container" style="height: 700px">
<GridMotion :items="images" /> <GridMotion :items="images" />
</div> </div>
<h2 className="demo-title-extra">Custom Content</h2>
<div class="relative py-6 rounded-3xl overflow-hidden demo-container" style="height: 700px">
<GridMotion :items="items" />
</div>
<PropTable :data="propData" /> <PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" /> <Dependencies :dependency-list="['gsap']" />
@@ -53,34 +45,14 @@ const propData = [
]; ];
const imageUrl = const imageUrl =
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'; 'https://images.unsplash.com/photo-1748370987492-eb390a61dcda?q=80&w=1364&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D';
const numberOfImages = 30; const numberOfImages = 30;
const images = Array.from({ length: numberOfImages }, () => imageUrl); const images = Array.from({ length: numberOfImages }, () => imageUrl);
const items = [
'Item 1',
`<div key="item-1">Custom Content</div>`,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 2',
`<div key="item-2">Custom Content</div>`,
'Item 4',
`<div key="item-3">Custom Content</div>`,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 5',
`<div key="item-4">Custom Content</div>`,
'Item 7',
`<div key="item-5">Custom Content</div>`,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 8',
`<div key="item-6">Custom Content</div>`,
'Item 10',
`<div key="item-7">Custom Content</div>`,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 11',
`<div key="item-8">Custom Content</div>`,
'Item 13',
`<div key="item-9">Custom Content</div>`,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 14'
];
</script> </script>
<style scoped>
.demo-container {
padding: 0;
overflow: hidden;
}
</style>