mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Merge pull request #10 from Utkarsh-Singhal-26/feat/grid-motion
Added <GridMotion /> background
This commit is contained in:
@@ -72,6 +72,7 @@ export const CATEGORIES = [
|
|||||||
'Squares',
|
'Squares',
|
||||||
'Iridescence',
|
'Iridescence',
|
||||||
'Threads',
|
'Threads',
|
||||||
|
'Grid Motion'
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const backgrounds = {
|
|||||||
'threads': () => import("../demo/Backgrounds/ThreadsDemo.vue"),
|
'threads': () => import("../demo/Backgrounds/ThreadsDemo.vue"),
|
||||||
'aurora': () => import("../demo/Backgrounds/AuroraDemo.vue"),
|
'aurora': () => import("../demo/Backgrounds/AuroraDemo.vue"),
|
||||||
'beams': () => import("../demo/Backgrounds/BeamsDemo.vue"),
|
'beams': () => import("../demo/Backgrounds/BeamsDemo.vue"),
|
||||||
|
'grid-motion': () => import("../demo/Backgrounds/GridMotionDemo.vue"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
|
|||||||
45
src/constants/code/Backgrounds/gridMotionCode.ts
Normal file
45
src/constants/code/Backgrounds/gridMotionCode.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import code from "@content/Backgrounds/GridMotion/GridMotion.vue?raw";
|
||||||
|
import type { CodeObject } from "../../../types/code";
|
||||||
|
|
||||||
|
export const gridMotion: CodeObject = {
|
||||||
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/GridMotion`,
|
||||||
|
installation: `npm i gsap`,
|
||||||
|
usage: `<template>
|
||||||
|
<GridMotion
|
||||||
|
:items="items"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import GridMotion from "./GridMotion.vue";
|
||||||
|
|
||||||
|
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-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>`,
|
||||||
|
code,
|
||||||
|
};
|
||||||
116
src/content/Backgrounds/GridMotion/GridMotion.vue
Normal file
116
src/content/Backgrounds/GridMotion/GridMotion.vue
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onBeforeUnmount, computed } from "vue";
|
||||||
|
import { gsap } from "gsap";
|
||||||
|
|
||||||
|
interface GridMotionProps {
|
||||||
|
items?: string[];
|
||||||
|
gradientColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<GridMotionProps>(), {
|
||||||
|
items: () => [],
|
||||||
|
gradientColor: "black",
|
||||||
|
});
|
||||||
|
|
||||||
|
const gridRef = ref<HTMLElement | null>(null);
|
||||||
|
const rowRefs = ref<HTMLElement[]>([]);
|
||||||
|
const mouseX = ref(window.innerWidth / 2);
|
||||||
|
|
||||||
|
const totalItems = 28;
|
||||||
|
const defaultItems = Array.from({ length: totalItems }, (_, i) => `Item ${i + 1}`);
|
||||||
|
const combinedItems = computed(() =>
|
||||||
|
props.items.length > 0 ? props.items.slice(0, totalItems) : defaultItems,
|
||||||
|
);
|
||||||
|
|
||||||
|
function isImage(item: string) {
|
||||||
|
return typeof item === "string" && item.startsWith("http");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTag(item: string) {
|
||||||
|
return typeof item === "string" && item.startsWith("<") && item.endsWith(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
gsap.ticker.lagSmoothing(0);
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
mouseX.value = e.clientX;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMotion = () => {
|
||||||
|
const maxMoveAmount = 300;
|
||||||
|
const baseDuration = 0.8;
|
||||||
|
const inertiaFactors = [0.6, 0.4, 0.3, 0.2];
|
||||||
|
|
||||||
|
rowRefs.value.forEach((row, index) => {
|
||||||
|
const direction = index % 2 === 0 ? 1 : -1;
|
||||||
|
const moveAmount =
|
||||||
|
((mouseX.value / window.innerWidth) * maxMoveAmount - maxMoveAmount / 2) * direction;
|
||||||
|
|
||||||
|
gsap.to(row, {
|
||||||
|
x: moveAmount,
|
||||||
|
duration: baseDuration + inertiaFactors[index % inertiaFactors.length],
|
||||||
|
ease: "power3.out",
|
||||||
|
overwrite: "auto",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeAnimation = gsap.ticker.add(updateMotion);
|
||||||
|
window.addEventListener("mousemove", handleMouseMove);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
removeAnimation();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="gridRef" class="w-full h-full overflow-hidden">
|
||||||
|
<section
|
||||||
|
class="relative flex justify-center items-center w-full h-screen overflow-hidden"
|
||||||
|
:style="{
|
||||||
|
background: `radial-gradient(circle, ${gradientColor} 0%, transparent 100%)`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="z-[4] absolute inset-0 bg-[length:250px] pointer-events-none"></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="z-[2] relative flex-none gap-4 grid grid-cols-1 grid-rows-4 w-[150vw] h-[150vh] rotate-[-15deg] origin-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="rowIndex in 4"
|
||||||
|
:key="rowIndex"
|
||||||
|
class="gap-4 grid grid-cols-7"
|
||||||
|
:style="{ willChange: 'transform, filter' }"
|
||||||
|
ref="rowRefs"
|
||||||
|
>
|
||||||
|
<div v-for="itemIndex in 7" :key="itemIndex" class="relative">
|
||||||
|
<div
|
||||||
|
class="relative flex justify-center items-center bg-[#111] rounded-[10px] w-full h-full overflow-hidden text-[1.5rem] text-white"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="isImage(combinedItems[(rowIndex - 1) * 7 + (itemIndex - 1)])"
|
||||||
|
class="top-0 left-0 absolute bg-cover bg-center w-full h-full"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: `url(${combinedItems[(rowIndex - 1) * 7 + (itemIndex - 1)]})`,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-else-if="isTag(combinedItems[(rowIndex - 1) * 7 + (itemIndex - 1)])"
|
||||||
|
class="z-[2] p-4 text-center"
|
||||||
|
v-html="combinedItems[(rowIndex - 1) * 7 + (itemIndex - 1)]"
|
||||||
|
></div>
|
||||||
|
<div v-else class="z-[1] p-4 text-center">
|
||||||
|
{{ combinedItems[(rowIndex - 1) * 7 + (itemIndex - 1)] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="top-0 left-0 relative w-full h-full pointer-events-none"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
85
src/demo/Backgrounds/GridMotionDemo.vue
Normal file
85
src/demo/Backgrounds/GridMotionDemo.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<h2 className="demo-title-extra">Images</h2>
|
||||||
|
<div class="relative py-6 rounded-3xl overflow-hidden demo-container" style="height: 700px">
|
||||||
|
<GridMotion :items="images" />
|
||||||
|
</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" />
|
||||||
|
<Dependencies :dependency-list="['gsap']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="gridMotion" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="gridMotion.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CliInstallation from "../../components/code/CliInstallation.vue";
|
||||||
|
import CodeExample from "../../components/code/CodeExample.vue";
|
||||||
|
import Dependencies from "../../components/code/Dependencies.vue";
|
||||||
|
import PropTable from "../../components/common/PropTable.vue";
|
||||||
|
import TabbedLayout from "../../components/common/TabbedLayout.vue";
|
||||||
|
import GridMotion from "../../content/Backgrounds/GridMotion/GridMotion.vue";
|
||||||
|
import { gridMotion } from "../../constants/code/Backgrounds/gridMotionCode";
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: "items",
|
||||||
|
type: "array",
|
||||||
|
default: "[]",
|
||||||
|
description:
|
||||||
|
"An array of items to display in the grid. Each item can be a string, JSX element, or an image URL.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gradientColor",
|
||||||
|
type: "string",
|
||||||
|
default: "black",
|
||||||
|
description: "Controls the color of the radial gradient used as the background.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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";
|
||||||
|
const numberOfImages = 30;
|
||||||
|
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-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>
|
||||||
Reference in New Issue
Block a user