mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Merge pull request #83 from Utkarsh-Singhal-26/main
Added <PrismaticBurst /> Background
This commit is contained in:
@@ -19,11 +19,11 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="hero-main-content">
|
<div class="hero-main-content">
|
||||||
<router-link to="/backgrounds/gradient-blinds" class="hero-new-badge-container">
|
<router-link to="/backgrounds/prismatic-burst" class="hero-new-badge-container">
|
||||||
<span class="hero-new-badge">New 🎉</span>
|
<span class="hero-new-badge">New 🎉</span>
|
||||||
<div class="hero-new-badge-text">
|
<div class="hero-new-badge-text">
|
||||||
<span>Gradient Blinds</span>
|
<span>Prismatic Burst</span>
|
||||||
<GoArrowRight />
|
<i class="pi-arrow-right pi" style="font-size: 0.8rem"></i>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Highlighted sidebar items
|
// Highlighted sidebar items
|
||||||
export const NEW = ['Gradient Blinds', 'Bubble Menu', 'Prism', 'Plasma', 'Electric Border', 'Target Cursor', 'Ripple Grid', 'Magic Bento', 'Galaxy', 'Glass Surface', 'Sticker Peel', 'Scroll Stack', 'Faulty Terminal', 'Pill Nav', 'Card Nav', 'Logo Loop'];
|
export const NEW = ['Gradient Blinds', 'Bubble Menu', 'Prism', 'Plasma', 'Electric Border', 'Target Cursor', 'Pill Nav', 'Card Nav', 'Logo Loop', 'Prismatic Burst'];
|
||||||
export const UPDATED = [];
|
export const UPDATED = [];
|
||||||
|
|
||||||
// Used for main sidebar navigation
|
// Used for main sidebar navigation
|
||||||
@@ -103,6 +103,7 @@ export const CATEGORIES = [
|
|||||||
'Dark Veil',
|
'Dark Veil',
|
||||||
'Dither',
|
'Dither',
|
||||||
'Gradient Blinds',
|
'Gradient Blinds',
|
||||||
|
'Prismatic Burst',
|
||||||
'Dot Grid',
|
'Dot Grid',
|
||||||
'Hyperspeed',
|
'Hyperspeed',
|
||||||
'Faulty Terminal',
|
'Faulty Terminal',
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ const backgrounds = {
|
|||||||
'plasma': () => import('../demo/Backgrounds/PlasmaDemo.vue'),
|
'plasma': () => import('../demo/Backgrounds/PlasmaDemo.vue'),
|
||||||
'prism': () => import('../demo/Backgrounds/PrismDemo.vue'),
|
'prism': () => import('../demo/Backgrounds/PrismDemo.vue'),
|
||||||
'gradient-blinds': () => import('../demo/Backgrounds/GradientBlindsDemo.vue'),
|
'gradient-blinds': () => import('../demo/Backgrounds/GradientBlindsDemo.vue'),
|
||||||
|
'prismatic-burst': () => import('../demo/Backgrounds/PrismaticBurstDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
|
|||||||
26
src/constants/code/Backgrounds/prismaticBurstCode.ts
Normal file
26
src/constants/code/Backgrounds/prismaticBurstCode.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import code from '@content/Backgrounds/PrismaticBurst/PrismaticBurst.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const prismaticBurst = createCodeObject(code, 'Backgrounds/PrismaticBurst', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<PrismaticBurst
|
||||||
|
animationType="rotate3d"
|
||||||
|
:intensity="2"
|
||||||
|
:speed="0.5"
|
||||||
|
:distort="1.0"
|
||||||
|
:paused="false"
|
||||||
|
:offset="{ x: 0, y: 0 }"
|
||||||
|
:hoverDampness="0.25"
|
||||||
|
:rayCount="24"
|
||||||
|
mixBlendMode="lighten"
|
||||||
|
:colors="['#ff007a', '#4d3dff', '#ffffff']"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import PrismaticBurst from "./PrismaticBurst.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
469
src/content/Backgrounds/PrismaticBurst/PrismaticBurst.vue
Normal file
469
src/content/Backgrounds/PrismaticBurst/PrismaticBurst.vue
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';
|
||||||
|
import { onMounted, onUnmounted, ref, useTemplateRef, watch, type CSSProperties } from 'vue';
|
||||||
|
|
||||||
|
type Offset = { x?: number | string; y?: number | string };
|
||||||
|
type AnimationType = 'rotate' | 'rotate3d' | 'hover';
|
||||||
|
|
||||||
|
export type PrismaticBurstProps = {
|
||||||
|
intensity?: number;
|
||||||
|
speed?: number;
|
||||||
|
animationType?: AnimationType;
|
||||||
|
colors?: string[];
|
||||||
|
distort?: number;
|
||||||
|
paused?: boolean;
|
||||||
|
offset?: Offset;
|
||||||
|
hoverDampness?: number;
|
||||||
|
rayCount?: number;
|
||||||
|
mixBlendMode?: CSSProperties['mixBlendMode'] | 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertexShader = `#version 300 es
|
||||||
|
in vec2 position;
|
||||||
|
in vec2 uv;
|
||||||
|
out vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
uniform vec2 uResolution;
|
||||||
|
uniform float uTime;
|
||||||
|
|
||||||
|
uniform float uIntensity;
|
||||||
|
uniform float uSpeed;
|
||||||
|
uniform int uAnimType;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform int uColorCount;
|
||||||
|
uniform float uDistort;
|
||||||
|
uniform vec2 uOffset;
|
||||||
|
uniform sampler2D uGradient;
|
||||||
|
uniform float uNoiseAmount;
|
||||||
|
uniform int uRayCount;
|
||||||
|
|
||||||
|
float hash21(vec2 p){
|
||||||
|
p = floor(p);
|
||||||
|
float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));
|
||||||
|
return fract(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }
|
||||||
|
|
||||||
|
float layeredNoise(vec2 fragPx){
|
||||||
|
vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);
|
||||||
|
vec2 q = rot30() * p;
|
||||||
|
float n = 0.0;
|
||||||
|
n += 0.40 * hash21(q);
|
||||||
|
n += 0.25 * hash21(q * 2.0 + 17.0);
|
||||||
|
n += 0.20 * hash21(q * 4.0 + 47.0);
|
||||||
|
n += 0.10 * hash21(q * 8.0 + 113.0);
|
||||||
|
n += 0.05 * hash21(q * 16.0 + 191.0);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){
|
||||||
|
float focal = res.y * max(dist, 1e-3);
|
||||||
|
return normalize(vec3(2.0 * (frag - offset) - res, focal));
|
||||||
|
}
|
||||||
|
|
||||||
|
float edgeFade(vec2 frag, vec2 res, vec2 offset){
|
||||||
|
vec2 toC = frag - 0.5 * res - offset;
|
||||||
|
float r = length(toC) / (0.5 * min(res.x, res.y));
|
||||||
|
float x = clamp(r, 0.0, 1.0);
|
||||||
|
float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
|
||||||
|
float s = q * 0.5;
|
||||||
|
s = pow(s, 1.5);
|
||||||
|
float tail = 1.0 - pow(1.0 - s, 2.0);
|
||||||
|
s = mix(s, tail, 0.2);
|
||||||
|
float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;
|
||||||
|
return clamp(s + dn, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }
|
||||||
|
mat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }
|
||||||
|
mat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }
|
||||||
|
|
||||||
|
vec3 sampleGradient(float t){
|
||||||
|
t = clamp(t, 0.0, 1.0);
|
||||||
|
return texture(uGradient, vec2(t, 0.5)).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 rot2(vec2 v, float a){
|
||||||
|
float s = sin(a), c = cos(a);
|
||||||
|
return mat2(c, -s, s, c) * v;
|
||||||
|
}
|
||||||
|
|
||||||
|
float bendAngle(vec3 q, float t){
|
||||||
|
float a = 0.8 * sin(q.x * 0.55 + t * 0.6)
|
||||||
|
+ 0.7 * sin(q.y * 0.50 - t * 0.5)
|
||||||
|
+ 0.6 * sin(q.z * 0.60 + t * 0.7);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
vec2 frag = gl_FragCoord.xy;
|
||||||
|
float t = uTime * uSpeed;
|
||||||
|
float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);
|
||||||
|
vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);
|
||||||
|
float marchT = 0.0;
|
||||||
|
vec3 col = vec3(0.0);
|
||||||
|
float n = layeredNoise(frag);
|
||||||
|
vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));
|
||||||
|
mat2 M2 = mat2(c.x, c.y, c.z, c.w);
|
||||||
|
float amp = clamp(uDistort, 0.0, 50.0) * 0.15;
|
||||||
|
|
||||||
|
mat3 rot3dMat = mat3(1.0);
|
||||||
|
if(uAnimType == 1){
|
||||||
|
vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);
|
||||||
|
rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);
|
||||||
|
}
|
||||||
|
mat3 hoverMat = mat3(1.0);
|
||||||
|
if(uAnimType == 2){
|
||||||
|
vec2 m = uMouse * 2.0 - 1.0;
|
||||||
|
vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);
|
||||||
|
hoverMat = rotY(ang.y) * rotX(ang.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 44; ++i) {
|
||||||
|
vec3 P = marchT * dir;
|
||||||
|
P.z -= 2.0;
|
||||||
|
float rad = length(P);
|
||||||
|
vec3 Pl = P * (10.0 / max(rad, 1e-6));
|
||||||
|
|
||||||
|
if(uAnimType == 0){
|
||||||
|
Pl.xz *= M2;
|
||||||
|
} else if(uAnimType == 1){
|
||||||
|
Pl = rot3dMat * Pl;
|
||||||
|
} else {
|
||||||
|
Pl = hoverMat * Pl;
|
||||||
|
}
|
||||||
|
|
||||||
|
float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;
|
||||||
|
|
||||||
|
float grow = smoothstep(0.35, 3.0, marchT);
|
||||||
|
float a1 = amp * grow * bendAngle(Pl * 0.6, t);
|
||||||
|
float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);
|
||||||
|
vec3 Pb = Pl;
|
||||||
|
Pb.xz = rot2(Pb.xz, a1);
|
||||||
|
Pb.xy = rot2(Pb.xy, a2);
|
||||||
|
|
||||||
|
float rayPattern = smoothstep(
|
||||||
|
0.5, 0.7,
|
||||||
|
sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *
|
||||||
|
sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uRayCount > 0) {
|
||||||
|
float ang = atan(Pb.y, Pb.x);
|
||||||
|
float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);
|
||||||
|
comb = pow(comb, 3.0);
|
||||||
|
rayPattern *= smoothstep(0.15, 0.95, comb);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 spectralDefault = 1.0 + vec3(
|
||||||
|
cos(marchT * 3.0 + 0.0),
|
||||||
|
cos(marchT * 3.0 + 1.0),
|
||||||
|
cos(marchT * 3.0 + 2.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
float saw = fract(marchT * 0.25);
|
||||||
|
float tRay = saw * saw * (3.0 - 2.0 * saw);
|
||||||
|
vec3 userGradient = 2.0 * sampleGradient(tRay);
|
||||||
|
vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;
|
||||||
|
vec3 base = (0.05 / (0.4 + stepLen))
|
||||||
|
* smoothstep(5.0, 0.0, rad)
|
||||||
|
* spectral;
|
||||||
|
|
||||||
|
col += base * rayPattern;
|
||||||
|
marchT += stepLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
col *= edgeFade(frag, uResolution, uOffset);
|
||||||
|
col *= uIntensity;
|
||||||
|
|
||||||
|
fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const hexToRgb01 = (hex: string): [number, number, number] => {
|
||||||
|
let h = hex.trim();
|
||||||
|
if (h.startsWith('#')) h = h.slice(1);
|
||||||
|
if (h.length === 3) {
|
||||||
|
const r = h[0],
|
||||||
|
g = h[1],
|
||||||
|
b = h[2];
|
||||||
|
h = r + r + g + g + b + b;
|
||||||
|
}
|
||||||
|
const intVal = parseInt(h, 16);
|
||||||
|
if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];
|
||||||
|
const r = ((intVal >> 16) & 255) / 255;
|
||||||
|
const g = ((intVal >> 8) & 255) / 255;
|
||||||
|
const b = (intVal & 255) / 255;
|
||||||
|
return [r, g, b];
|
||||||
|
};
|
||||||
|
|
||||||
|
const toPx = (v: number | string | undefined): number => {
|
||||||
|
if (v == null) return 0;
|
||||||
|
if (typeof v === 'number') return v;
|
||||||
|
const s = String(v).trim();
|
||||||
|
const num = parseFloat(s.replace('px', ''));
|
||||||
|
return isNaN(num) ? 0 : num;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<PrismaticBurstProps>(), {
|
||||||
|
intensity: 2,
|
||||||
|
speed: 0.5,
|
||||||
|
animationType: 'rotate3d',
|
||||||
|
distort: 0,
|
||||||
|
paused: false,
|
||||||
|
offset: () => ({ x: 0, y: 0 }),
|
||||||
|
hoverDampness: 0,
|
||||||
|
mixBlendMode: 'lighten'
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef('containerRef');
|
||||||
|
const programRef = ref<Program | null>(null);
|
||||||
|
const rendererRef = ref<Renderer | null>(null);
|
||||||
|
const mouseTargetRef = ref<[number, number]>([0.5, 0.5]);
|
||||||
|
const mouseSmoothRef = ref<[number, number]>([0.5, 0.5]);
|
||||||
|
const pausedRef = ref<boolean>(props.paused);
|
||||||
|
const gradTexRef = ref<Texture | null>(null);
|
||||||
|
const hoverDampRef = ref<number>(props.hoverDampness);
|
||||||
|
const isVisibleRef = ref<boolean>(true);
|
||||||
|
const meshRef = ref<Mesh | null>(null);
|
||||||
|
const triRef = ref<Triangle | null>(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const container = containerRef.value;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
|
const renderer = new Renderer({ dpr, alpha: false, antialias: false });
|
||||||
|
rendererRef.value = renderer;
|
||||||
|
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.canvas.style.position = 'absolute';
|
||||||
|
gl.canvas.style.inset = '0';
|
||||||
|
gl.canvas.style.width = '100%';
|
||||||
|
gl.canvas.style.height = '100%';
|
||||||
|
gl.canvas.style.mixBlendMode = props.mixBlendMode && props.mixBlendMode !== 'none' ? props.mixBlendMode : '';
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
const white = new Uint8Array([255, 255, 255, 255]);
|
||||||
|
const gradientTex = new Texture(gl, {
|
||||||
|
image: white,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
generateMipmaps: false,
|
||||||
|
flipY: false
|
||||||
|
});
|
||||||
|
|
||||||
|
gradientTex.minFilter = gl.LINEAR;
|
||||||
|
gradientTex.magFilter = gl.LINEAR;
|
||||||
|
gradientTex.wrapS = gl.CLAMP_TO_EDGE;
|
||||||
|
gradientTex.wrapT = gl.CLAMP_TO_EDGE;
|
||||||
|
gradTexRef.value = gradientTex;
|
||||||
|
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uResolution: { value: [1, 1] as [number, number] },
|
||||||
|
uTime: { value: 0 },
|
||||||
|
|
||||||
|
uIntensity: { value: 1 },
|
||||||
|
uSpeed: { value: 1 },
|
||||||
|
uAnimType: { value: 0 },
|
||||||
|
uMouse: { value: [0.5, 0.5] as [number, number] },
|
||||||
|
uColorCount: { value: 0 },
|
||||||
|
uDistort: { value: 0 },
|
||||||
|
uOffset: { value: [0, 0] as [number, number] },
|
||||||
|
uGradient: { value: gradientTex },
|
||||||
|
uNoiseAmount: { value: 0.8 },
|
||||||
|
uRayCount: { value: 0 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
programRef.value = program;
|
||||||
|
|
||||||
|
const triangle = new Triangle(gl);
|
||||||
|
const mesh = new Mesh(gl, { geometry: triangle, program });
|
||||||
|
triRef.value = triangle;
|
||||||
|
meshRef.value = mesh;
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
const w = container.clientWidth || 1;
|
||||||
|
const h = container.clientHeight || 1;
|
||||||
|
renderer.setSize(w, h);
|
||||||
|
program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];
|
||||||
|
};
|
||||||
|
|
||||||
|
let ro: ResizeObserver | null = null;
|
||||||
|
if ('ResizeObserver' in window) {
|
||||||
|
ro = new ResizeObserver(resize);
|
||||||
|
ro.observe(container);
|
||||||
|
} else {
|
||||||
|
(window as Window).addEventListener('resize', resize);
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
|
||||||
|
const onPointer = (e: PointerEvent) => {
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const x = (e.clientX - rect.left) / Math.max(rect.width, 1);
|
||||||
|
const y = (e.clientY - rect.top) / Math.max(rect.height, 1);
|
||||||
|
mouseTargetRef.value = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];
|
||||||
|
};
|
||||||
|
container.addEventListener('pointermove', onPointer, { passive: true });
|
||||||
|
|
||||||
|
let io: IntersectionObserver | null = null;
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
io = new IntersectionObserver(
|
||||||
|
entries => {
|
||||||
|
if (entries[0]) isVisibleRef.value = entries[0].isIntersecting;
|
||||||
|
},
|
||||||
|
{ root: null, threshold: 0.01 }
|
||||||
|
);
|
||||||
|
io.observe(container);
|
||||||
|
}
|
||||||
|
const onVis = () => {};
|
||||||
|
document.addEventListener('visibilitychange', onVis);
|
||||||
|
|
||||||
|
let raf = 0;
|
||||||
|
let last = performance.now();
|
||||||
|
let accumTime = 0;
|
||||||
|
|
||||||
|
const update = (now: number) => {
|
||||||
|
const dt = Math.max(0, now - last) * 0.001;
|
||||||
|
last = now;
|
||||||
|
const visible = isVisibleRef.value && !document.hidden;
|
||||||
|
if (!pausedRef.value) accumTime += dt;
|
||||||
|
if (!visible) {
|
||||||
|
raf = requestAnimationFrame(update);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.value)) * 0.5;
|
||||||
|
const alpha = 1 - Math.exp(-dt / tau);
|
||||||
|
const tgt = mouseTargetRef.value;
|
||||||
|
const sm = mouseSmoothRef.value;
|
||||||
|
sm[0] += (tgt[0] - sm[0]) * alpha;
|
||||||
|
sm[1] += (tgt[1] - sm[1]) * alpha;
|
||||||
|
program.uniforms.uMouse.value = sm as [number, number];
|
||||||
|
program.uniforms.uTime.value = accumTime;
|
||||||
|
renderer.render({ scene: meshRef.value! });
|
||||||
|
raf = requestAnimationFrame(update);
|
||||||
|
};
|
||||||
|
raf = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
container.removeEventListener('pointermove', onPointer);
|
||||||
|
ro?.disconnect();
|
||||||
|
if (!ro) window.removeEventListener('resize', resize);
|
||||||
|
io?.disconnect();
|
||||||
|
document.removeEventListener('visibilitychange', onVis);
|
||||||
|
try {
|
||||||
|
container.removeChild(gl.canvas);
|
||||||
|
} catch (e) {
|
||||||
|
void e;
|
||||||
|
}
|
||||||
|
meshRef.value = null;
|
||||||
|
triRef.value = null;
|
||||||
|
programRef.value = null;
|
||||||
|
try {
|
||||||
|
const glCtx = rendererRef.value?.gl;
|
||||||
|
if (glCtx && gradTexRef.value?.texture) glCtx.deleteTexture(gradTexRef.value.texture);
|
||||||
|
} catch (e) {
|
||||||
|
void e;
|
||||||
|
}
|
||||||
|
rendererRef.value = null;
|
||||||
|
gradTexRef.value = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.paused,
|
||||||
|
v => (pausedRef.value = v)
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.hoverDampness,
|
||||||
|
v => (hoverDampRef.value = v)
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.mixBlendMode,
|
||||||
|
mode => {
|
||||||
|
const canvas = rendererRef.value?.gl?.canvas as HTMLCanvasElement | undefined;
|
||||||
|
if (canvas) {
|
||||||
|
canvas.style.mixBlendMode = mode && mode !== 'none' ? mode : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.intensity, props.speed, props.animationType, props.colors, props.distort, props.offset, props.rayCount],
|
||||||
|
() => {
|
||||||
|
const program = programRef.value;
|
||||||
|
const renderer = rendererRef.value;
|
||||||
|
const gradTex = gradTexRef.value;
|
||||||
|
if (!program || !renderer || !gradTex) return;
|
||||||
|
|
||||||
|
program.uniforms.uIntensity.value = props.intensity ?? 1;
|
||||||
|
program.uniforms.uSpeed.value = props.speed ?? 1;
|
||||||
|
|
||||||
|
const animTypeMap: Record<AnimationType, number> = {
|
||||||
|
rotate: 0,
|
||||||
|
rotate3d: 1,
|
||||||
|
hover: 2
|
||||||
|
};
|
||||||
|
program.uniforms.uAnimType.value = animTypeMap[props.animationType ?? 'rotate'];
|
||||||
|
|
||||||
|
program.uniforms.uDistort.value = typeof props.distort === 'number' ? props.distort : 0;
|
||||||
|
|
||||||
|
const ox = toPx(props.offset?.x);
|
||||||
|
const oy = toPx(props.offset?.y);
|
||||||
|
program.uniforms.uOffset.value = [ox, oy];
|
||||||
|
program.uniforms.uRayCount.value = Math.max(0, Math.floor(props.rayCount ?? 0));
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
if (Array.isArray(props.colors) && props.colors.length > 0) {
|
||||||
|
const gl = renderer.gl;
|
||||||
|
const capped = props.colors.slice(0, 64);
|
||||||
|
count = capped.length;
|
||||||
|
const data = new Uint8Array(count * 4);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const [r, g, b] = hexToRgb01(capped[i]);
|
||||||
|
data[i * 4 + 0] = Math.round(r * 255);
|
||||||
|
data[i * 4 + 1] = Math.round(g * 255);
|
||||||
|
data[i * 4 + 2] = Math.round(b * 255);
|
||||||
|
data[i * 4 + 3] = 255;
|
||||||
|
}
|
||||||
|
gradTex.image = data;
|
||||||
|
gradTex.width = count;
|
||||||
|
gradTex.height = 1;
|
||||||
|
gradTex.minFilter = gl.LINEAR;
|
||||||
|
gradTex.magFilter = gl.LINEAR;
|
||||||
|
gradTex.wrapS = gl.CLAMP_TO_EDGE;
|
||||||
|
gradTex.wrapT = gl.CLAMP_TO_EDGE;
|
||||||
|
gradTex.flipY = false;
|
||||||
|
gradTex.generateMipmaps = false;
|
||||||
|
gradTex.format = gl.RGBA;
|
||||||
|
gradTex.type = gl.UNSIGNED_BYTE;
|
||||||
|
gradTex.needsUpdate = true;
|
||||||
|
} else {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
program.uniforms.uColorCount.value = count;
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative w-full h-full overflow-hidden" ref="containerRef" />
|
||||||
|
</template>
|
||||||
154
src/demo/Backgrounds/PrismaticBurstDemo.vue
Normal file
154
src/demo/Backgrounds/PrismaticBurstDemo.vue
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<PrismaticBurst
|
||||||
|
:animation-type="animationType"
|
||||||
|
:intensity="intensity"
|
||||||
|
:speed="speed"
|
||||||
|
:distort="distort"
|
||||||
|
:hover-dampness="hoverDampness"
|
||||||
|
:ray-count="rayCount || undefined"
|
||||||
|
v-bind="userColors.length ? { colors: userColors } : {}"
|
||||||
|
/>
|
||||||
|
<BackgroundContent pill-text="New Background" headline="A burst of dancing colors, beautifully unleashed" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<PreviewColor title="Color 1" v-model="color0" />
|
||||||
|
<PreviewColor title="Color 2" v-model="color1" />
|
||||||
|
<PreviewColor title="Color 3" v-model="color2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSelect title="Animation Type" v-model="animationType" :options="animationOptions" />
|
||||||
|
<PreviewSlider :min="0.1" :max="5" :step="0.1" v-model="intensity" title="Intensity" />
|
||||||
|
<PreviewSlider :min="0" :max="2" :step="0.5" v-model="speed" title="Speed" />
|
||||||
|
<PreviewSlider :min="0" :max="10" :step="0.1" v-model="distort" title="Distort" />
|
||||||
|
<PreviewSlider :min="0" :max="64" :step="1" v-model="rayCount" title="Ray Count" />
|
||||||
|
<PreviewSlider
|
||||||
|
v-if="animationType === 'hover'"
|
||||||
|
:min="0"
|
||||||
|
:max="1"
|
||||||
|
:step="0.01"
|
||||||
|
v-model="hoverDampness"
|
||||||
|
title="Hover Dampness"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="prismaticBurst" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="prismaticBurst.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { prismaticBurst } from '@/constants/code/Backgrounds/prismaticBurstCode';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import CliInstallation from '../../components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '../../components/code/CodeExample.vue';
|
||||||
|
import Dependencies from '../../components/code/Dependencies.vue';
|
||||||
|
import BackgroundContent from '../../components/common/BackgroundContent.vue';
|
||||||
|
import Customize from '../../components/common/Customize.vue';
|
||||||
|
import PreviewColor from '../../components/common/PreviewColor.vue';
|
||||||
|
import PreviewSelect from '../../components/common/PreviewSelect.vue';
|
||||||
|
import PreviewSlider from '../../components/common/PreviewSlider.vue';
|
||||||
|
import PropTable from '../../components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '../../components/common/TabbedLayout.vue';
|
||||||
|
import PrismaticBurst from '../../content/Backgrounds/PrismaticBurst/PrismaticBurst.vue';
|
||||||
|
|
||||||
|
const animationOptions = [
|
||||||
|
{ value: 'rotate', label: 'Rotate' },
|
||||||
|
{ value: 'rotate3d', label: 'Rotate 3D' },
|
||||||
|
{ value: 'hover', label: 'Hover' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const animationType = ref<'rotate' | 'rotate3d' | 'hover'>('rotate3d');
|
||||||
|
const intensity = ref(2);
|
||||||
|
const speed = ref(0.5);
|
||||||
|
const distort = ref(0);
|
||||||
|
const hoverDampness = ref(0.25);
|
||||||
|
const rayCount = ref(0);
|
||||||
|
const color0 = ref('');
|
||||||
|
const color1 = ref('');
|
||||||
|
const color2 = ref('');
|
||||||
|
|
||||||
|
const userColors = computed(() => [color0.value, color1.value, color2.value].filter(Boolean) as string[]);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'intensity',
|
||||||
|
type: 'number',
|
||||||
|
default: '2',
|
||||||
|
description: 'Overall brightness multiplier applied after accumulation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'speed',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'Global time multiplier controlling ray motion & distortion.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'animationType',
|
||||||
|
type: '"rotate" | "rotate3d" | "hover"',
|
||||||
|
default: '"rotate3d"',
|
||||||
|
description: 'Core motion style: planar rotation, full 3D rotation, or pointer hover orbit'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'colors',
|
||||||
|
type: 'string[]',
|
||||||
|
default: '[]',
|
||||||
|
description: 'Optional array of hex colors used as a gradient (otherwise spectral)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'distort',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Amount of bend/distortion applied to marching space (adds organic wobble)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'paused',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Freeze time progression when true (animation stops)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
type: '{ x?: number|string; y?: number|string }',
|
||||||
|
default: '{ x: 0, y: 0 }',
|
||||||
|
description: 'Pixel (or CSS length) offset of focal origin from center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hoverDampness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: "Smoothing factor (0-1) for pointer tracking when animationType='hover'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rayCount',
|
||||||
|
type: 'number',
|
||||||
|
default: 'undefined',
|
||||||
|
description: 'If > 0 applies an angular comb filter to produce discrete ray spokes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mixBlendMode',
|
||||||
|
type: "CSSProperties['mixBlendMode'] | 'none'",
|
||||||
|
default: '"lighten"',
|
||||||
|
description: "Canvas CSS mix-blend-mode (e.g. lighten, screen) or 'none' for normal"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user