mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
Merge pull request #63 from Gazoon007/feat/light-rays
Create <LightRays /> background
This commit is contained in:
@@ -114,6 +114,7 @@ export const CATEGORIES = [
|
|||||||
'Liquid Chrome',
|
'Liquid Chrome',
|
||||||
'Grid Distortion',
|
'Grid Distortion',
|
||||||
'Galaxy',
|
'Galaxy',
|
||||||
|
'Light Rays',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ const backgrounds = {
|
|||||||
'ripple-grid': () => import('../demo/Backgrounds/RippleGridDemo.vue'),
|
'ripple-grid': () => import('../demo/Backgrounds/RippleGridDemo.vue'),
|
||||||
'galaxy': () => import('../demo/Backgrounds/GalaxyDemo.vue'),
|
'galaxy': () => import('../demo/Backgrounds/GalaxyDemo.vue'),
|
||||||
'faulty-terminal': () => import('../demo/Backgrounds/FaultyTerminalDemo.vue'),
|
'faulty-terminal': () => import('../demo/Backgrounds/FaultyTerminalDemo.vue'),
|
||||||
|
'light-rays': () => import('../demo/Backgrounds/LightRaysDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
|
|||||||
26
src/constants/code/Backgrounds/lightRaysCode.ts
Normal file
26
src/constants/code/Backgrounds/lightRaysCode.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import code from '@content/Backgrounds/LightRays/LightRays.vue?raw';
|
||||||
|
import { createCodeObject } from '@/types/code';
|
||||||
|
|
||||||
|
export const lightRays = createCodeObject(code, 'Backgrounds/LightRays', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div class="w-full h-[600px] relative">
|
||||||
|
<LightRays
|
||||||
|
rays-origin="top-center"
|
||||||
|
rays-color="#00ffff"
|
||||||
|
:rays-speed="1.5"
|
||||||
|
:light-spread="0.8"
|
||||||
|
:ray-length="1.2"
|
||||||
|
:follow-mouse="true"
|
||||||
|
:mouse-influence="0.1"
|
||||||
|
:noise-amount="0.1"
|
||||||
|
:distortion="0.05"
|
||||||
|
class-name="custom-rays"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LightRays from "./LightRays.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
525
src/content/Backgrounds/LightRays/LightRays.vue
Normal file
525
src/content/Backgrounds/LightRays/LightRays.vue
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="containerRef"
|
||||||
|
:class="[
|
||||||
|
'w-full h-full relative pointer-events-none z-[3] overflow-hidden',
|
||||||
|
className
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, watch, useTemplateRef, computed, nextTick } from 'vue';
|
||||||
|
import { Renderer, Program, Triangle, Mesh } from 'ogl';
|
||||||
|
|
||||||
|
export type RaysOrigin =
|
||||||
|
| 'top-center'
|
||||||
|
| 'top-left'
|
||||||
|
| 'top-right'
|
||||||
|
| 'right'
|
||||||
|
| 'left'
|
||||||
|
| 'bottom-center'
|
||||||
|
| 'bottom-right'
|
||||||
|
| 'bottom-left';
|
||||||
|
|
||||||
|
interface LightRaysProps {
|
||||||
|
raysOrigin?: RaysOrigin;
|
||||||
|
raysColor?: string;
|
||||||
|
raysSpeed?: number;
|
||||||
|
lightSpread?: number;
|
||||||
|
rayLength?: number;
|
||||||
|
pulsating?: boolean;
|
||||||
|
fadeDistance?: number;
|
||||||
|
saturation?: number;
|
||||||
|
followMouse?: boolean;
|
||||||
|
mouseInfluence?: number;
|
||||||
|
noiseAmount?: number;
|
||||||
|
distortion?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MousePosition {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnchorAndDirection {
|
||||||
|
anchor: [number, number];
|
||||||
|
dir: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebGLUniforms {
|
||||||
|
iTime: { value: number };
|
||||||
|
iResolution: { value: [number, number] };
|
||||||
|
rayPos: { value: [number, number] };
|
||||||
|
rayDir: { value: [number, number] };
|
||||||
|
raysColor: { value: [number, number, number] };
|
||||||
|
raysSpeed: { value: number };
|
||||||
|
lightSpread: { value: number };
|
||||||
|
rayLength: { value: number };
|
||||||
|
pulsating: { value: number };
|
||||||
|
fadeDistance: { value: number };
|
||||||
|
saturation: { value: number };
|
||||||
|
mousePos: { value: [number, number] };
|
||||||
|
mouseInfluence: { value: number };
|
||||||
|
noiseAmount: { value: number };
|
||||||
|
distortion: { value: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<LightRaysProps>(), {
|
||||||
|
raysOrigin: 'top-center',
|
||||||
|
raysColor: '#ffffff',
|
||||||
|
raysSpeed: 1,
|
||||||
|
lightSpread: 1,
|
||||||
|
rayLength: 2,
|
||||||
|
pulsating: false,
|
||||||
|
fadeDistance: 1.0,
|
||||||
|
saturation: 1.0,
|
||||||
|
followMouse: true,
|
||||||
|
mouseInfluence: 0.1,
|
||||||
|
noiseAmount: 0.0,
|
||||||
|
distortion: 0.0,
|
||||||
|
className: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
|
||||||
|
const uniformsRef = ref<WebGLUniforms | null>(null);
|
||||||
|
const rendererRef = ref<Renderer | null>(null);
|
||||||
|
const mouseRef = ref<MousePosition>({ x: 0.5, y: 0.5 });
|
||||||
|
const smoothMouseRef = ref<MousePosition>({ x: 0.5, y: 0.5 });
|
||||||
|
const animationIdRef = ref<number | null>(null);
|
||||||
|
const meshRef = ref<Mesh | null>(null);
|
||||||
|
const cleanupFunctionRef = ref<(() => void) | null>(null);
|
||||||
|
const isVisible = ref<boolean>(false);
|
||||||
|
const observerRef = ref<IntersectionObserver | null>(null);
|
||||||
|
const resizeTimeoutRef = ref<number | null>(null);
|
||||||
|
|
||||||
|
const rgbColor = computed<[number, number, number]>(() => hexToRgb(props.raysColor));
|
||||||
|
const pulsatingValue = computed<number>(() => props.pulsating ? 1.0 : 0.0);
|
||||||
|
const devicePixelRatio = computed<number>(() => Math.min(window.devicePixelRatio || 1, 2));
|
||||||
|
|
||||||
|
const hexToRgb = (hex: string): [number, number, number] => {
|
||||||
|
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return m
|
||||||
|
? [
|
||||||
|
parseInt(m[1], 16) / 255,
|
||||||
|
parseInt(m[2], 16) / 255,
|
||||||
|
parseInt(m[3], 16) / 255,
|
||||||
|
]
|
||||||
|
: [1, 1, 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAnchorAndDir = (origin: RaysOrigin, w: number, h: number): AnchorAndDirection => {
|
||||||
|
const outside = 0.2;
|
||||||
|
switch (origin) {
|
||||||
|
case 'top-left':
|
||||||
|
return { anchor: [0, -outside * h], dir: [0, 1] };
|
||||||
|
case 'top-right':
|
||||||
|
return { anchor: [w, -outside * h], dir: [0, 1] };
|
||||||
|
case 'left':
|
||||||
|
return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };
|
||||||
|
case 'right':
|
||||||
|
return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };
|
||||||
|
case 'bottom-left':
|
||||||
|
return { anchor: [0, (1 + outside) * h], dir: [0, -1] };
|
||||||
|
case 'bottom-center':
|
||||||
|
return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };
|
||||||
|
case 'bottom-right':
|
||||||
|
return { anchor: [w, (1 + outside) * h], dir: [0, -1] };
|
||||||
|
default:
|
||||||
|
return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedUpdatePlacement = (() => {
|
||||||
|
let timeoutId: number | null = null;
|
||||||
|
|
||||||
|
return (updateFn: () => void): void => {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
timeoutId = window.setTimeout(() => {
|
||||||
|
updateFn();
|
||||||
|
timeoutId = null;
|
||||||
|
}, 16);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const vertexShader: string = `
|
||||||
|
attribute vec2 position;
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = position * 0.5 + 0.5;
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const fragmentShader: string = `precision highp float;
|
||||||
|
|
||||||
|
uniform float iTime;
|
||||||
|
uniform vec2 iResolution;
|
||||||
|
|
||||||
|
uniform vec2 rayPos;
|
||||||
|
uniform vec2 rayDir;
|
||||||
|
uniform vec3 raysColor;
|
||||||
|
uniform float raysSpeed;
|
||||||
|
uniform float lightSpread;
|
||||||
|
uniform float rayLength;
|
||||||
|
uniform float pulsating;
|
||||||
|
uniform float fadeDistance;
|
||||||
|
uniform float saturation;
|
||||||
|
uniform vec2 mousePos;
|
||||||
|
uniform float mouseInfluence;
|
||||||
|
uniform float noiseAmount;
|
||||||
|
uniform float distortion;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
float noise(vec2 st) {
|
||||||
|
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord,
|
||||||
|
float seedA, float seedB, float speed) {
|
||||||
|
vec2 sourceToCoord = coord - raySource;
|
||||||
|
vec2 dirNorm = normalize(sourceToCoord);
|
||||||
|
float cosAngle = dot(dirNorm, rayRefDirection);
|
||||||
|
|
||||||
|
float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;
|
||||||
|
|
||||||
|
float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
|
||||||
|
|
||||||
|
float distance = length(sourceToCoord);
|
||||||
|
float maxDistance = iResolution.x * rayLength;
|
||||||
|
float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
|
||||||
|
|
||||||
|
float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);
|
||||||
|
float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;
|
||||||
|
|
||||||
|
float baseStrength = clamp(
|
||||||
|
(0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
|
||||||
|
(0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
|
||||||
|
0.0, 1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
|
||||||
|
|
||||||
|
vec2 finalRayDir = rayDir;
|
||||||
|
if (mouseInfluence > 0.0) {
|
||||||
|
vec2 mouseScreenPos = mousePos * iResolution.xy;
|
||||||
|
vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
|
||||||
|
finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 rays1 = vec4(1.0) *
|
||||||
|
rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349,
|
||||||
|
1.5 * raysSpeed);
|
||||||
|
vec4 rays2 = vec4(1.0) *
|
||||||
|
rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234,
|
||||||
|
1.1 * raysSpeed);
|
||||||
|
|
||||||
|
fragColor = rays1 * 0.5 + rays2 * 0.4;
|
||||||
|
|
||||||
|
if (noiseAmount > 0.0) {
|
||||||
|
float n = noise(coord * 0.01 + iTime * 0.1);
|
||||||
|
fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
float brightness = 1.0 - (coord.y / iResolution.y);
|
||||||
|
fragColor.x *= 0.1 + brightness * 0.8;
|
||||||
|
fragColor.y *= 0.3 + brightness * 0.6;
|
||||||
|
fragColor.z *= 0.5 + brightness * 0.5;
|
||||||
|
|
||||||
|
if (saturation != 1.0) {
|
||||||
|
float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragColor.rgb *= raysColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color;
|
||||||
|
mainImage(color, gl_FragCoord.xy);
|
||||||
|
gl_FragColor = color;
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const initializeWebGL = async (): Promise<void> => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const renderer = new Renderer({
|
||||||
|
dpr: devicePixelRatio.value,
|
||||||
|
alpha: true,
|
||||||
|
antialias: false,
|
||||||
|
powerPreference: 'high-performance'
|
||||||
|
});
|
||||||
|
rendererRef.value = renderer;
|
||||||
|
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.canvas.style.width = '100%';
|
||||||
|
gl.canvas.style.height = '100%';
|
||||||
|
|
||||||
|
while (containerRef.value.firstChild) {
|
||||||
|
containerRef.value.removeChild(containerRef.value.firstChild);
|
||||||
|
}
|
||||||
|
containerRef.value.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
const uniforms: WebGLUniforms = {
|
||||||
|
iTime: { value: 0 },
|
||||||
|
iResolution: { value: [1, 1] },
|
||||||
|
rayPos: { value: [0, 0] },
|
||||||
|
rayDir: { value: [0, 1] },
|
||||||
|
raysColor: { value: rgbColor.value },
|
||||||
|
raysSpeed: { value: props.raysSpeed },
|
||||||
|
lightSpread: { value: props.lightSpread },
|
||||||
|
rayLength: { value: props.rayLength },
|
||||||
|
pulsating: { value: pulsatingValue.value },
|
||||||
|
fadeDistance: { value: props.fadeDistance },
|
||||||
|
saturation: { value: props.saturation },
|
||||||
|
mousePos: { value: [0.5, 0.5] },
|
||||||
|
mouseInfluence: { value: props.mouseInfluence },
|
||||||
|
noiseAmount: { value: props.noiseAmount },
|
||||||
|
distortion: { value: props.distortion },
|
||||||
|
};
|
||||||
|
uniformsRef.value = uniforms;
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms,
|
||||||
|
});
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
meshRef.value = mesh;
|
||||||
|
|
||||||
|
const updatePlacement = (): void => {
|
||||||
|
if (!containerRef.value || !renderer) return;
|
||||||
|
|
||||||
|
renderer.dpr = devicePixelRatio.value;
|
||||||
|
|
||||||
|
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.value;
|
||||||
|
renderer.setSize(wCSS, hCSS);
|
||||||
|
|
||||||
|
const dpr = renderer.dpr;
|
||||||
|
const w = wCSS * dpr;
|
||||||
|
const h = hCSS * dpr;
|
||||||
|
|
||||||
|
uniforms.iResolution.value = [w, h];
|
||||||
|
|
||||||
|
const { anchor, dir } = getAnchorAndDir(props.raysOrigin, w, h);
|
||||||
|
uniforms.rayPos.value = anchor;
|
||||||
|
uniforms.rayDir.value = dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loop = (t: number): void => {
|
||||||
|
if (!rendererRef.value || !uniformsRef.value || !meshRef.value || !isVisible.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uniforms.iTime.value = t * 0.001;
|
||||||
|
|
||||||
|
if (props.followMouse && props.mouseInfluence > 0.0) {
|
||||||
|
const smoothing = 0.92;
|
||||||
|
|
||||||
|
smoothMouseRef.value.x =
|
||||||
|
smoothMouseRef.value.x * smoothing +
|
||||||
|
mouseRef.value.x * (1 - smoothing);
|
||||||
|
smoothMouseRef.value.y =
|
||||||
|
smoothMouseRef.value.y * smoothing +
|
||||||
|
mouseRef.value.y * (1 - smoothing);
|
||||||
|
|
||||||
|
uniforms.mousePos.value = [
|
||||||
|
smoothMouseRef.value.x,
|
||||||
|
smoothMouseRef.value.y,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
renderer.render({ scene: mesh });
|
||||||
|
animationIdRef.value = requestAnimationFrame(loop);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('WebGL rendering error:', error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = (): void => {
|
||||||
|
debouncedUpdatePlacement(updatePlacement);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize, { passive: true });
|
||||||
|
updatePlacement();
|
||||||
|
animationIdRef.value = requestAnimationFrame(loop);
|
||||||
|
|
||||||
|
cleanupFunctionRef.value = (): void => {
|
||||||
|
if (animationIdRef.value) {
|
||||||
|
cancelAnimationFrame(animationIdRef.value);
|
||||||
|
animationIdRef.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
if (resizeTimeoutRef.value) {
|
||||||
|
clearTimeout(resizeTimeoutRef.value);
|
||||||
|
resizeTimeoutRef.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renderer) {
|
||||||
|
try {
|
||||||
|
const canvas = renderer.gl.canvas;
|
||||||
|
const loseContextExt =
|
||||||
|
renderer.gl.getExtension('WEBGL_lose_context');
|
||||||
|
if (loseContextExt) {
|
||||||
|
loseContextExt.loseContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canvas && canvas.parentNode) {
|
||||||
|
canvas.parentNode.removeChild(canvas);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error during WebGL cleanup:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rendererRef.value = null;
|
||||||
|
uniformsRef.value = null;
|
||||||
|
meshRef.value = null;
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize WebGL:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mouseThrottleId: number | null = null;
|
||||||
|
const handleMouseMove = (e: MouseEvent): void => {
|
||||||
|
if (!containerRef.value || !rendererRef.value) return;
|
||||||
|
|
||||||
|
if (mouseThrottleId) return;
|
||||||
|
|
||||||
|
mouseThrottleId = requestAnimationFrame(() => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
|
||||||
|
const rect = containerRef.value.getBoundingClientRect();
|
||||||
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
|
const y = (e.clientY - rect.top) / rect.height;
|
||||||
|
mouseRef.value = { x, y };
|
||||||
|
mouseThrottleId = null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted((): void => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
|
||||||
|
observerRef.value = new IntersectionObserver(
|
||||||
|
(entries: IntersectionObserverEntry[]): void => {
|
||||||
|
const entry = entries[0];
|
||||||
|
isVisible.value = entry.isIntersecting;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
rootMargin: '50px'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
observerRef.value.observe(containerRef.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isVisible, (newVisible: boolean): void => {
|
||||||
|
if (newVisible && containerRef.value) {
|
||||||
|
if (cleanupFunctionRef.value) {
|
||||||
|
cleanupFunctionRef.value();
|
||||||
|
cleanupFunctionRef.value = null;
|
||||||
|
}
|
||||||
|
initializeWebGL();
|
||||||
|
} else if (!newVisible && cleanupFunctionRef.value) {
|
||||||
|
if (animationIdRef.value) {
|
||||||
|
cancelAnimationFrame(animationIdRef.value);
|
||||||
|
animationIdRef.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[
|
||||||
|
() => props.raysColor,
|
||||||
|
() => props.raysSpeed,
|
||||||
|
() => props.lightSpread,
|
||||||
|
() => props.raysOrigin,
|
||||||
|
() => props.rayLength,
|
||||||
|
() => props.pulsating,
|
||||||
|
() => props.fadeDistance,
|
||||||
|
() => props.saturation,
|
||||||
|
() => props.mouseInfluence,
|
||||||
|
() => props.noiseAmount,
|
||||||
|
() => props.distortion,
|
||||||
|
],
|
||||||
|
(): void => {
|
||||||
|
if (!uniformsRef.value || !containerRef.value || !rendererRef.value) return;
|
||||||
|
|
||||||
|
const u = uniformsRef.value;
|
||||||
|
const renderer = rendererRef.value;
|
||||||
|
|
||||||
|
u.raysColor.value = rgbColor.value;
|
||||||
|
u.raysSpeed.value = props.raysSpeed;
|
||||||
|
u.lightSpread.value = props.lightSpread;
|
||||||
|
u.rayLength.value = props.rayLength;
|
||||||
|
u.pulsating.value = pulsatingValue.value;
|
||||||
|
u.fadeDistance.value = props.fadeDistance;
|
||||||
|
u.saturation.value = props.saturation;
|
||||||
|
u.mouseInfluence.value = props.mouseInfluence;
|
||||||
|
u.noiseAmount.value = props.noiseAmount;
|
||||||
|
u.distortion.value = props.distortion;
|
||||||
|
|
||||||
|
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.value;
|
||||||
|
const dpr = renderer.dpr;
|
||||||
|
const { anchor, dir } = getAnchorAndDir(props.raysOrigin, wCSS * dpr, hCSS * dpr);
|
||||||
|
u.rayPos.value = anchor;
|
||||||
|
u.rayDir.value = dir;
|
||||||
|
},
|
||||||
|
{ flush: 'post' }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.followMouse,
|
||||||
|
(newFollowMouse: boolean): void => {
|
||||||
|
if (newFollowMouse) {
|
||||||
|
window.addEventListener('mousemove', handleMouseMove, { passive: true });
|
||||||
|
} else {
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
if (mouseThrottleId) {
|
||||||
|
cancelAnimationFrame(mouseThrottleId);
|
||||||
|
mouseThrottleId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted((): void => {
|
||||||
|
if (observerRef.value) {
|
||||||
|
observerRef.value.disconnect();
|
||||||
|
observerRef.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupFunctionRef.value) {
|
||||||
|
cleanupFunctionRef.value();
|
||||||
|
cleanupFunctionRef.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseThrottleId) {
|
||||||
|
cancelAnimationFrame(mouseThrottleId);
|
||||||
|
mouseThrottleId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
243
src/demo/Backgrounds/LightRaysDemo.vue
Normal file
243
src/demo/Backgrounds/LightRaysDemo.vue
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="h-[600px] overflow-hidden demo-container relative">
|
||||||
|
<LightRays
|
||||||
|
:rays-origin="raysOrigin"
|
||||||
|
:rays-color="raysColor"
|
||||||
|
:rays-speed="raysSpeed"
|
||||||
|
:light-spread="lightSpread"
|
||||||
|
:ray-length="rayLength"
|
||||||
|
:pulsating="pulsating"
|
||||||
|
:fade-distance="fadeDistance"
|
||||||
|
:saturation="saturation"
|
||||||
|
:follow-mouse="true"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
:noise-amount="noiseAmount"
|
||||||
|
:distortion="distortion"
|
||||||
|
class="w-full h-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="May these lights guide you on your path" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewColor
|
||||||
|
title="Rays Color"
|
||||||
|
v-model="raysColor"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSelect
|
||||||
|
title="Rays Origin"
|
||||||
|
v-model="raysOrigin"
|
||||||
|
:options="raysOriginOptions"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Rays Speed"
|
||||||
|
v-model="raysSpeed"
|
||||||
|
:min="0.1"
|
||||||
|
:max="3"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Light Spread"
|
||||||
|
v-model="lightSpread"
|
||||||
|
:min="0.1"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Ray Length"
|
||||||
|
v-model="rayLength"
|
||||||
|
:min="0.5"
|
||||||
|
:max="3"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Fade Distance"
|
||||||
|
v-model="fadeDistance"
|
||||||
|
:min="0.5"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Saturation"
|
||||||
|
v-model="saturation"
|
||||||
|
:min="0"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Mouse Influence"
|
||||||
|
v-model="mouseInfluence"
|
||||||
|
:min="0"
|
||||||
|
:max="1"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Noise Amount"
|
||||||
|
v-model="noiseAmount"
|
||||||
|
:min="0"
|
||||||
|
:max="0.5"
|
||||||
|
:step="0.01"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Distortion"
|
||||||
|
v-model="distortion"
|
||||||
|
:min="0"
|
||||||
|
:max="1"
|
||||||
|
:step="0.1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSwitch title="Pulsating" v-model="pulsating" />
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="lightRays" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="lightRays.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import Dependencies from '@/components/code/Dependencies.vue';
|
||||||
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
|
import Customize from '@/components/common/Customize.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSelect from '@/components/common/PreviewSelect.vue';
|
||||||
|
import PreviewColor from '@/components/common/PreviewColor.vue';
|
||||||
|
import BackgroundContent from '@/components/common/BackgroundContent.vue';
|
||||||
|
import LightRays, { type RaysOrigin } from '../../content/Backgrounds/LightRays/LightRays.vue';
|
||||||
|
import { lightRays } from '@/constants/code/Backgrounds/lightRaysCode';
|
||||||
|
|
||||||
|
const raysOrigin = ref<RaysOrigin>('top-center');
|
||||||
|
const raysColor = ref('#ffffff');
|
||||||
|
const raysSpeed = ref(1);
|
||||||
|
const lightSpread = ref(0.5);
|
||||||
|
const rayLength = ref(1.0);
|
||||||
|
const pulsating = ref(false);
|
||||||
|
const fadeDistance = ref(1.0);
|
||||||
|
const saturation = ref(1.0);
|
||||||
|
const mouseInfluence = ref(0.5);
|
||||||
|
const noiseAmount = ref(0.0);
|
||||||
|
const distortion = ref(0.0);
|
||||||
|
|
||||||
|
const raysOriginOptions = [
|
||||||
|
{ value: 'top-center', label: 'Top' },
|
||||||
|
{ value: 'right', label: 'Right' },
|
||||||
|
{ value: 'left', label: 'Left' },
|
||||||
|
{ value: 'bottom-center', label: 'Bottom' },
|
||||||
|
{ value: 'top-left', label: 'Top Left' },
|
||||||
|
{ value: 'top-right', label: 'Top Right' },
|
||||||
|
{ value: 'bottom-left', label: 'Bottom Left' },
|
||||||
|
{ value: 'bottom-right', label: 'Bottom Right' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'raysOrigin',
|
||||||
|
type: 'RaysOrigin',
|
||||||
|
default: '"top-center"',
|
||||||
|
description:
|
||||||
|
"Origin position of the light rays. Options: 'top-center', 'top-left', 'top-right', 'right', 'left', 'bottom-center', 'bottom-right', 'bottom-left'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'raysColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#ffffff"',
|
||||||
|
description: 'Color of the light rays in hex format'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'raysSpeed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1',
|
||||||
|
description: 'Animation speed of the rays'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lightSpread',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'How wide the light rays spread. Lower values = tighter rays, higher values = wider spread'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rayLength',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Maximum length/reach of the rays'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pulsating',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Enable pulsing animation effect'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fadeDistance',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'How far rays fade out from origin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'saturation',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Color saturation level (0-1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'followMouse',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Make rays rotate towards the mouse cursor'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mouseInfluence',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'How much mouse affects rays (0-1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noiseAmount',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.0',
|
||||||
|
description: 'Add noise/grain to rays (0-1)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'distortion',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.0',
|
||||||
|
description: 'Apply wave distortion to rays'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'className',
|
||||||
|
type: 'string',
|
||||||
|
default: '""',
|
||||||
|
description: 'Additional CSS classes to apply to the container'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user