added animations
This commit is contained in:
parent
e995513829
commit
f2dd78dc80
124
components/FlickeringGrid.vue
Normal file
124
components/FlickeringGrid.vue
Normal 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>
|
||||||
|
|
@ -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>
|
||||||
|
21
composables/useScrollReveal.js
Normal file
21
composables/useScrollReveal.js
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
1310
package-lock.json
generated
1310
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="min-h-screen relative bg-background">
|
<main class="min-h-screen relative bg-[#070F11]">
|
||||||
<hero />
|
<FlickeringGrid
|
||||||
<!-- // Remove this once you clone the template -->
|
class="absolute inset-0 z-0"
|
||||||
<!-- <github /> -->
|
color="#0CE77E"
|
||||||
<!-- // Remove this once you clone the template -->
|
:squareSize="3"
|
||||||
<section
|
:gridGap="10"
|
||||||
class="relative mx-auto max-w-5xl px-4 sm:px-6 lg:px-8 overflow-hidden text-primary"
|
:maxOpacity="0.2"
|
||||||
>
|
/>
|
||||||
<post v-for="post in data" :content="post" />
|
<div class="relative z-10">
|
||||||
</section>
|
<hero />
|
||||||
|
<section
|
||||||
|
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" />
|
||||||
|
</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()
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user