Separated out Background.vue
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 58s

That'll make using it in the homepage easier
This commit is contained in:
thepaperpilot 2024-06-20 08:33:48 -05:00
parent 831060a57e
commit 411b2ac293
4 changed files with 61 additions and 86 deletions

View file

@ -1,58 +1,34 @@
<template> <template>
<TresCanvas> <TresGroup ref="groupRef" v-if="renderer">
<TresOrthographicCamera ref="camera" :position="[0, 0, 10]" /> <TresMesh v-for="i in rows * cols" :position="[((i % cols) - cols / 2) * 304, (Math.floor((i - 1) / cols) - rows / 2) * 304, 0]" >
<TresGroup ref="blobRef"> <TresShapeGeometry :args="[shapes]" />
<TresMesh v-for="i in rows * cols" :position="[((i % cols) - cols / 2) * 304, (Math.floor((i - 1) / cols) - rows / 2) * 304, 0]" > <TresShaderMaterial :vertexShader="vertexShader" :fragmentShader="fragmentShader" :uniforms="uniforms" :blending="AdditiveBlending" />
<TresShapeGeometry :args="[shapes]" /> </TresMesh>
<TresShaderMaterial :vertexShader="vertexShader" :fragmentShader="fragmentShader" :uniforms="uniforms" :blending="AdditiveBlending" /> </TresGroup>
</TresMesh>
</TresGroup>
<TresAmbientLight :intensity="1" />
</TresCanvas>
<div ref="resizeListener" class="resize-listener" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, shallowRef, onMounted, onUnmounted, watch } from "vue"; import { useLoader, useRenderLoop, useTresContext } from '@tresjs/core';
import { TresCanvas, useLoader, useRenderLoop } from '@tresjs/core'; import { AdditiveBlending, Group, Vector2 } from "three";
import { SVGLoader } from "three/examples/jsm/loaders/SVGLoader.js"; import { SVGLoader } from "three/examples/jsm/loaders/SVGLoader.js";
import { computed, onMounted, onUnmounted, ref, shallowRef } from "vue";
import noise from "./noise.glsl?raw"; import noise from "./noise.glsl?raw";
import { AdditiveBlending, Vector2 } from "three";
const camera = ref(); const { renderer, sizes } = useTresContext();
// Load SVG // Load SVG
const { paths } = await useLoader(SVGLoader, '/circuit-board.svg'); const { paths } = await useLoader(SVGLoader, '/circuit-board.svg');
const shapes = paths.map(path => SVGLoader.createShapes(path)).reduce((acc, curr) => [...acc, ...curr]); const shapes = paths.map(path => SVGLoader.createShapes(path)).reduce((acc, curr) => [...acc, ...curr]);
// Handle canvas size const rows = computed(() => Math.ceil(sizes.height.value / 304));
const width = ref(0); const cols = computed(() => Math.ceil(sizes.width.value / 304));
const height = ref(0);
const rows = computed(() => Math.ceil(height.value / 304));
const cols = computed(() => Math.ceil(width.value / 304));
const resizeObserver = new ResizeObserver(updateSize);
const resizeListener = ref<Element | null>(null);
function updateSize() {
const rect = resizeListener.value?.getBoundingClientRect();
width.value = rect?.width ?? 0;
height.value = rect?.height ?? 0;
}
watch([width, height, camera], ([width, height, camera]) => {
if (camera) {
camera.left = 0;
camera.bottom = 0
camera.right = width;
camera.top = height;
camera.updateProjectionMatrix();
}
});
// Handle mouse position // Handle mouse position
const mousePos = ref(new Vector2(Infinity, Infinity)); const mousePos = ref(new Vector2(Infinity, Infinity));
function updateMousePos(event: MouseEvent) { function updateMousePos(event: MouseEvent) {
mousePos.value = new Vector2(event.screenX, window.screen.availHeight - event.screenY); mousePos.value = new Vector2(event.screenX, window.screen.availHeight - event.screenY);
if (blobRef.value) { if (groupRef.value) {
blobRef.value.children.forEach(child => { groupRef.value.children.forEach(child => {
child.material.uniforms.uMouse.value = mousePos.value; child.material.uniforms.uMouse.value = mousePos.value;
}); });
} }
@ -60,8 +36,8 @@ function updateMousePos(event: MouseEvent) {
function handleMouseLeave(event: MouseEvent) { function handleMouseLeave(event: MouseEvent) {
if (!event.relatedTarget) { if (!event.relatedTarget) {
mousePos.value = new Vector2(Infinity, Infinity); mousePos.value = new Vector2(Infinity, Infinity);
if (blobRef.value) { if (groupRef.value) {
blobRef.value.children.forEach(child => { groupRef.value.children.forEach(child => {
child.material.uniforms.uMouse.value = mousePos.value; child.material.uniforms.uMouse.value = mousePos.value;
}); });
} }
@ -70,19 +46,16 @@ function handleMouseLeave(event: MouseEvent) {
// Setup window listeners // Setup window listeners
onMounted(() => { onMounted(() => {
if (resizeListener.value != null) { window.addEventListener("mousemove", updateMousePos);
resizeObserver.observe(resizeListener.value); window.addEventListener("mouseout", handleMouseLeave);
}
window.addEventListener("mousemove", updateMousePos);
window.addEventListener("mouseout", handleMouseLeave);
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener("mousemove", updateMousePos); window.removeEventListener("mousemove", updateMousePos);
window.removeEventListener("mouseout", handleMouseLeave); window.removeEventListener("mouseout", handleMouseLeave);
}); });
// Shaders // Shaders
const blobRef = shallowRef<Element | null>(null); const groupRef = shallowRef<Group | null>(null);
const uniforms = { const uniforms = {
uTime: { value: 0 }, uTime: { value: 0 },
@ -98,8 +71,7 @@ void main() {
gl_Position = projectionMatrix * viewPosition; gl_Position = projectionMatrix * viewPosition;
vUv = uv; vUv = uv;
} }
` `;
const fragmentShader = noise + ` const fragmentShader = noise + `
precision mediump float; precision mediump float;
uniform float uTime; uniform float uTime;
@ -112,26 +84,14 @@ void main() {
alpha += max(0., snoise(vec3(gl_FragCoord.xy / 304., uTime / 4.))); alpha += max(0., snoise(vec3(gl_FragCoord.xy / 304., uTime / 4.)));
gl_FragColor = vec4(0, 0, 0, alpha); gl_FragColor = vec4(0, 0, 0, alpha);
} }
` `;
const { onLoop } = useRenderLoop();
const { onLoop } = useRenderLoop();
onLoop(({ elapsed }) => { onLoop(({ elapsed }) => {
if (blobRef.value) { if (groupRef.value) {
blobRef.value.children.forEach(child => { groupRef.value.children.forEach(child => {
child.material.uniforms.uTime.value = elapsed; child.material.uniforms.uTime.value = elapsed;
}); });
} }
}); });
</script> </script>
<style scoped>
.resize-listener {
position: absolute;
top: 0px;
left: 0;
right: -4px;
bottom: 5px;
z-index: -10;
pointer-events: none;
}
</style>

View file

@ -0,0 +1,21 @@
<template>
<TresOrthographicCamera :position="[0, 0, 10]" />
</template>
<script setup lang="ts">
import { useTresContext } from '@tresjs/core';
import { OrthographicCamera } from 'three';
import { watch } from "vue";
const context = useTresContext();
watch([context.sizes.width, context.sizes.height, context.camera], ([width, height, camera]) => {
const cam = camera as OrthographicCamera;
if (cam) {
cam.left = 0;
cam.bottom = 0
cam.right = width;
cam.top = height;
cam.updateProjectionMatrix();
}
});
</script>

View file

@ -3,11 +3,15 @@
<template #layout-top> <template #layout-top>
<NolebaseHighlightTargetedHeading /> <NolebaseHighlightTargetedHeading />
<ClientOnly> <ClientOnly>
<Suspense> <div class="background">
<div class="background"> <TresCanvas>
<Background /> <Camera />
</div> <TresAmbientLight :intensity="1" />
</Suspense> <Suspense>
<Background />
</Suspense>
</TresCanvas>
</div>
</ClientOnly> </ClientOnly>
</template> </template>
<template #layout-bottom> <template #layout-bottom>
@ -22,11 +26,14 @@
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import DefaultTheme from 'vitepress/theme'
import { NolebaseHighlightTargetedHeading } from '@nolebase/vitepress-plugin-highlight-targeted-heading/client' import { NolebaseHighlightTargetedHeading } from '@nolebase/vitepress-plugin-highlight-targeted-heading/client'
import { TresCanvas } from '@tresjs/core'
import '@nolebase/vitepress-plugin-highlight-targeted-heading/client/style.css' import '@nolebase/vitepress-plugin-highlight-targeted-heading/client/style.css'
import './custom.css' import DefaultTheme from 'vitepress/theme'
import Background from './Background.vue' import Background from './Background.vue'
import Camera from './Camera.vue'
import OrbitControls from './OrbitControls.vue'
import './custom.css'
</script> </script>
<style scoped> <style scoped>

View file

@ -12,16 +12,3 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
extend({ OrbitControls }); extend({ OrbitControls });
const { camera, renderer } = useTresContext(); const { camera, renderer } = useTresContext();
</script> </script>
<style scoped>
.background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
overflow: hidden;
}
</style>