added animations

This commit is contained in:
shialoth 2025-04-25 19:38:09 +05:30
parent e995513829
commit f2dd78dc80
7 changed files with 1764 additions and 582 deletions

View File

@ -0,0 +1,124 @@
<template>
<div ref="containerRef" :class="['h-full w-full', className]">
<canvas
ref="canvasRef"
class="pointer-events-none"
:style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }"
/>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, watchEffect } from 'vue';
const props = defineProps({
squareSize: { type: Number, default: 4 },
gridGap: { type: Number, default: 6 },
flickerChance: { type: Number, default: 0.3 },
color: { type: String, default: 'rgb(0, 0, 0)' },
width: Number,
height: Number,
className: String,
maxOpacity: { type: Number, default: 0.3 },
});
const canvasRef = ref(null);
const containerRef = ref(null);
const canvasSize = ref({ width: 0, height: 0 });
const isInView = ref(false);
function toRGBA(color) {
if (process.server) return `rgba(0, 0, 0,`;
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
const ctx = canvas.getContext('2d');
if (!ctx) return `rgba(255, 0, 0,`;
ctx.fillStyle = color;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
return `rgba(${r}, ${g}, ${b},`;
}
const memoizedColor = toRGBA(props.color);
onMounted(() => {
const canvas = canvasRef.value;
const container = containerRef.value;
if (!canvas || !container) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const dpr = window.devicePixelRatio || 1;
let cols, rows, squares, animationFrameId, lastTime = 0;
const setupCanvas = () => {
const w = props.width || container.clientWidth;
const h = props.height || container.clientHeight;
canvasSize.value = { width: w, height: h };
canvas.width = w * dpr;
canvas.height = h * dpr;
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;
cols = Math.floor(w / (props.squareSize + props.gridGap));
rows = Math.floor(h / (props.squareSize + props.gridGap));
squares = new Float32Array(cols * rows).map(() => Math.random() * props.maxOpacity);
};
const updateSquares = (deltaTime) => {
for (let i = 0; i < squares.length; i++) {
if (Math.random() < props.flickerChance * deltaTime) {
squares[i] = Math.random() * props.maxOpacity;
}
}
};
const drawGrid = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const opacity = squares[i * rows + j];
ctx.fillStyle = `${memoizedColor}${opacity})`;
ctx.fillRect(
i * (props.squareSize + props.gridGap) * dpr,
j * (props.squareSize + props.gridGap) * dpr,
props.squareSize * dpr,
props.squareSize * dpr
);
}
}
};
const animate = (time) => {
if (!isInView.value) return;
const deltaTime = (time - lastTime) / 1000;
lastTime = time;
updateSquares(deltaTime);
drawGrid();
animationFrameId = requestAnimationFrame(animate);
};
const resizeObserver = new ResizeObserver(() => setupCanvas());
resizeObserver.observe(container);
const intersectionObserver = new IntersectionObserver(([entry]) => {
isInView.value = entry.isIntersecting;
if (isInView.value) animationFrameId = requestAnimationFrame(animate);
});
intersectionObserver.observe(canvas);
setupCanvas();
onUnmounted(() => {
cancelAnimationFrame(animationFrameId);
resizeObserver.disconnect();
intersectionObserver.disconnect();
});
});
</script>

View File

@ -1,5 +1,5 @@
<template> <template>
<article class="md:flex"> <article ref="postEl" class="md:flex">
<h2 class="content-date h-full mt-px"> <h2 class="content-date h-full mt-px">
<a href="#2022-06-23">{{ content.date }}</a> <a href="#2022-06-23">{{ content.date }}</a>
</h2> </h2>
@ -20,9 +20,18 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'
import { useScrollReveal } from '@/composables/useScrollReveal'
const props = defineProps({ const props = defineProps({
content: Object, content: Object,
}); })
const postEl = ref(null)
onMounted(() => {
useScrollReveal(postEl)
})
</script> </script>
<style> <style>

View File

@ -0,0 +1,21 @@
// composables/useScrollReveal.js
import { useMotion } from '@vueuse/motion'
export const useScrollReveal = (elRef) => {
useMotion(elRef, {
initial: {
opacity: 0,
y: 40,
},
visible: {
opacity: 1,
y: 0,
transition: {
type: 'spring',
stiffness: 250,
damping: 20,
},
},
onAppear: true,
})
}

1308
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,5 +12,8 @@
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"nuxt": "^3.1.1", "nuxt": "^3.1.1",
"nuxt-icon": "^0.2.10" "nuxt-icon": "^0.2.10"
},
"dependencies": {
"@vueuse/motion": "^3.0.3"
} }
} }

View File

@ -1,19 +1,27 @@
<template> <template>
<main class="min-h-screen relative bg-background"> <main class="min-h-screen relative bg-[#070F11]">
<FlickeringGrid
class="absolute inset-0 z-0"
color="#0CE77E"
:squareSize="3"
:gridGap="10"
:maxOpacity="0.2"
/>
<div class="relative z-10">
<hero /> <hero />
<!-- // Remove this once you clone the template -->
<!-- <github /> -->
<!-- // Remove this once you clone the template -->
<section <section
class="relative mx-auto max-w-5xl px-4 sm:px-6 lg:px-8 overflow-hidden text-primary" class="relative mx-auto max-w-5xl px-4 sm:px-6 lg:px-8 overflow-hidden text-primary"
> >
<post v-for="post in data" :content="post" /> <post v-for="post in data" :content="post" />
</section> </section>
</div>
</main> </main>
</template> </template>
<script setup> <script setup>
import FlickeringGrid from "@/components/FlickeringGrid.vue";
import seoConfig from "../seoConfig/index"; import seoConfig from "../seoConfig/index";
useHead({ useHead({
title: seoConfig.title, title: seoConfig.title,
meta: [ meta: [
@ -33,6 +41,7 @@ useHead({
}, },
], ],
}); });
const { data } = await useAsyncData("feed", () => const { data } = await useAsyncData("feed", () =>
queryContent("/posts").find() queryContent("/posts").find()
); );

846
yarn.lock

File diff suppressed because it is too large Load Diff