mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
FEAT: 🎉 Added <LightPillar /> background
This commit is contained in:
BIN
public/assets/videos/lightpillar.mp4
Normal file
BIN
public/assets/videos/lightpillar.mp4
Normal file
Binary file not shown.
BIN
public/assets/videos/lightpillar.webm
Normal file
BIN
public/assets/videos/lightpillar.webm
Normal file
Binary file not shown.
@@ -19,10 +19,10 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="hero-main-content">
|
<div class="hero-main-content">
|
||||||
<router-link to="/backgrounds/floating-lines" class="hero-new-badge-container">
|
<router-link to="/backgrounds/light-pillar" 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>Floating Lines</span>
|
<span>Light Pillar</span>
|
||||||
<i class="pi-arrow-right pi" style="font-size: 0.8rem"></i>
|
<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 = ['Color Bends', 'Ghost Cursor', 'Laser Flow', 'Liquid Ether', 'Pixel Blast', 'Floating Lines'];
|
export const NEW = ['Color Bends', 'Ghost Cursor', 'Laser Flow', 'Liquid Ether', 'Pixel Blast', 'Floating Lines', 'Light Pillar'];
|
||||||
export const UPDATED = [];
|
export const UPDATED = [];
|
||||||
|
|
||||||
// Used for main sidebar navigation
|
// Used for main sidebar navigation
|
||||||
@@ -123,6 +123,7 @@ export const CATEGORIES = [
|
|||||||
'Iridescence',
|
'Iridescence',
|
||||||
'Letter Glitch',
|
'Letter Glitch',
|
||||||
'Lightning',
|
'Lightning',
|
||||||
|
'Light Pillar',
|
||||||
'Light Rays',
|
'Light Rays',
|
||||||
'Liquid Chrome',
|
'Liquid Chrome',
|
||||||
'Liquid Ether',
|
'Liquid Ether',
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ const backgrounds = {
|
|||||||
'liquid-ether': () => import('../demo/Backgrounds/LiquidEtherDemo.vue'),
|
'liquid-ether': () => import('../demo/Backgrounds/LiquidEtherDemo.vue'),
|
||||||
'color-bends': () => import('../demo/Backgrounds/ColorBendsDemo.vue'),
|
'color-bends': () => import('../demo/Backgrounds/ColorBendsDemo.vue'),
|
||||||
'floating-lines': () => import('../demo/Backgrounds/FloatingLinesDemo.vue'),
|
'floating-lines': () => import('../demo/Backgrounds/FloatingLinesDemo.vue'),
|
||||||
|
'light-pillar': () => import('../demo/Backgrounds/LightPillarDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
|
|||||||
@@ -948,5 +948,13 @@ export const componentMetadata: ComponentMetadata = {
|
|||||||
name: 'LiquidEther',
|
name: 'LiquidEther',
|
||||||
docsUrl: 'https://vue-bits.dev/backgrounds/liquid-ether',
|
docsUrl: 'https://vue-bits.dev/backgrounds/liquid-ether',
|
||||||
tags: []
|
tags: []
|
||||||
|
},
|
||||||
|
'Backgrounds/LightPillar': {
|
||||||
|
videoUrl: '/assets/videos/lightpillar.webm',
|
||||||
|
description: 'Vertical pillar of light with glow effects.',
|
||||||
|
category: 'Backgrounds',
|
||||||
|
name: 'LightPillar',
|
||||||
|
docsUrl: 'https://vue-bits.dev/backgrounds/light-pillar',
|
||||||
|
tags: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
27
src/constants/code/Backgrounds/lightPillarCode.ts
Normal file
27
src/constants/code/Backgrounds/lightPillarCode.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import code from '@content/Backgrounds/LightPillar/LightPillar.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const lightPillar = createCodeObject(code, 'Backgrounds/LightPillar', {
|
||||||
|
installation: `npm install three`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<LightPillar
|
||||||
|
topColor="#48FF28"
|
||||||
|
bottomColor="#9EF19E"
|
||||||
|
:intensity="1.0"
|
||||||
|
:rotationSpeed="0.3"
|
||||||
|
:glowAmount="0.005"
|
||||||
|
:pillarWidth="3.0"
|
||||||
|
:pillarHeight="0.4"
|
||||||
|
:noiseIntensity="0.5"
|
||||||
|
:pillarRotation="0"
|
||||||
|
:interactive="false"
|
||||||
|
mixBlendMode="normal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import LightPillar from './LightPillar.vue'
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
402
src/content/Backgrounds/LightPillar/LightPillar.vue
Normal file
402
src/content/Backgrounds/LightPillar/LightPillar.vue
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import {
|
||||||
|
onBeforeMount,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
useTemplateRef,
|
||||||
|
watch,
|
||||||
|
type CSSProperties
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
interface LightPillarProps {
|
||||||
|
topColor?: string;
|
||||||
|
bottomColor?: string;
|
||||||
|
intensity?: number;
|
||||||
|
rotationSpeed?: number;
|
||||||
|
interactive?: boolean;
|
||||||
|
className?: string;
|
||||||
|
glowAmount?: number;
|
||||||
|
pillarWidth?: number;
|
||||||
|
pillarHeight?: number;
|
||||||
|
noiseIntensity?: number;
|
||||||
|
mixBlendMode?: CSSProperties['mixBlendMode'];
|
||||||
|
pillarRotation?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<LightPillarProps>(), {
|
||||||
|
topColor: '#48FF28',
|
||||||
|
bottomColor: '#9EF19E',
|
||||||
|
intensity: 1.0,
|
||||||
|
rotationSpeed: 0.3,
|
||||||
|
interactive: false,
|
||||||
|
className: '',
|
||||||
|
glowAmount: 0.005,
|
||||||
|
pillarWidth: 3.0,
|
||||||
|
pillarHeight: 0.4,
|
||||||
|
noiseIntensity: 0.5,
|
||||||
|
mixBlendMode: 'screen',
|
||||||
|
pillarRotation: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef('containerRef');
|
||||||
|
const rafRef = ref<number | null>(null);
|
||||||
|
const rendererRef = shallowRef<THREE.WebGLRenderer | null>(null);
|
||||||
|
const materialRef = shallowRef<THREE.ShaderMaterial | null>(null);
|
||||||
|
const sceneRef = shallowRef<THREE.Scene | null>(null);
|
||||||
|
const cameraRef = shallowRef<THREE.OrthographicCamera | null>(null);
|
||||||
|
const geometryRef = shallowRef<THREE.PlaneGeometry | null>(null);
|
||||||
|
const mouseRef = ref<THREE.Vector2>(new THREE.Vector2(0, 0));
|
||||||
|
const timeRef = ref<number>(0);
|
||||||
|
const webGLSupported = ref<boolean>(true);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||||
|
|
||||||
|
if (!gl) {
|
||||||
|
webGLSupported.value = false;
|
||||||
|
console.warn('WebGL is not supported in this browser');
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
const setup = () => {
|
||||||
|
if (!containerRef.value || !webGLSupported.value) return;
|
||||||
|
|
||||||
|
const container = containerRef.value;
|
||||||
|
const width = container.clientWidth;
|
||||||
|
const height = container.clientHeight;
|
||||||
|
|
||||||
|
// Scene setup
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
sceneRef.value = scene;
|
||||||
|
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
||||||
|
cameraRef.value = camera;
|
||||||
|
|
||||||
|
let renderer: THREE.WebGLRenderer;
|
||||||
|
try {
|
||||||
|
renderer = new THREE.WebGLRenderer({
|
||||||
|
antialias: false,
|
||||||
|
alpha: true,
|
||||||
|
powerPreference: 'high-performance',
|
||||||
|
precision: 'lowp',
|
||||||
|
stencil: false,
|
||||||
|
depth: false
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create WebGL renderer:', error);
|
||||||
|
webGLSupported.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
|
container.appendChild(renderer.domElement);
|
||||||
|
rendererRef.value = renderer;
|
||||||
|
|
||||||
|
// Convert hex colors to RGB
|
||||||
|
const parseColor = (hex: string): THREE.Vector3 => {
|
||||||
|
const color = new THREE.Color(hex);
|
||||||
|
return new THREE.Vector3(color.r, color.g, color.b);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shader material
|
||||||
|
const vertexShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec2 uResolution;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform vec3 uTopColor;
|
||||||
|
uniform vec3 uBottomColor;
|
||||||
|
uniform float uIntensity;
|
||||||
|
uniform bool uInteractive;
|
||||||
|
uniform float uGlowAmount;
|
||||||
|
uniform float uPillarWidth;
|
||||||
|
uniform float uPillarHeight;
|
||||||
|
uniform float uNoiseIntensity;
|
||||||
|
uniform float uPillarRotation;
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
const float PI = 3.141592653589793;
|
||||||
|
const float EPSILON = 0.001;
|
||||||
|
const float E = 2.71828182845904523536;
|
||||||
|
const float HALF = 0.5;
|
||||||
|
|
||||||
|
mat2 rot(float angle) {
|
||||||
|
float s = sin(angle);
|
||||||
|
float c = cos(angle);
|
||||||
|
return mat2(c, -s, s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procedural noise function
|
||||||
|
float noise(vec2 coord) {
|
||||||
|
float G = E;
|
||||||
|
vec2 r = (G * sin(G * coord));
|
||||||
|
return fract(r.x * r.y * (1.0 + coord.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply layered wave deformation to position
|
||||||
|
vec3 applyWaveDeformation(vec3 pos, float timeOffset) {
|
||||||
|
float frequency = 1.0;
|
||||||
|
float amplitude = 1.0;
|
||||||
|
vec3 deformed = pos;
|
||||||
|
|
||||||
|
for(float i = 0.0; i < 4.0; i++) {
|
||||||
|
deformed.xz *= rot(0.4);
|
||||||
|
float phase = timeOffset * i * 2.0;
|
||||||
|
vec3 oscillation = cos(deformed.zxy * frequency - phase);
|
||||||
|
deformed += oscillation * amplitude;
|
||||||
|
frequency *= 2.0;
|
||||||
|
amplitude *= HALF;
|
||||||
|
}
|
||||||
|
return deformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polynomial smooth blending between two values
|
||||||
|
float blendMin(float a, float b, float k) {
|
||||||
|
float scaledK = k * 4.0;
|
||||||
|
float h = max(scaledK - abs(a - b), 0.0);
|
||||||
|
return min(a, b) - h * h * 0.25 / scaledK;
|
||||||
|
}
|
||||||
|
|
||||||
|
float blendMax(float a, float b, float k) {
|
||||||
|
return -blendMin(-a, -b, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fragCoord = vUv * uResolution;
|
||||||
|
vec2 uv = (fragCoord * 2.0 - uResolution) / uResolution.y;
|
||||||
|
|
||||||
|
// Apply 2D rotation to UV coordinates
|
||||||
|
float rotAngle = uPillarRotation * PI / 180.0;
|
||||||
|
uv *= rot(rotAngle);
|
||||||
|
|
||||||
|
vec3 origin = vec3(0.0, 0.0, -10.0);
|
||||||
|
vec3 direction = normalize(vec3(uv, 1.0));
|
||||||
|
|
||||||
|
float maxDepth = 50.0;
|
||||||
|
float depth = 0.1;
|
||||||
|
|
||||||
|
mat2 rotX = rot(uTime * 0.3);
|
||||||
|
if(uInteractive && length(uMouse) > 0.0) {
|
||||||
|
rotX = rot(uMouse.x * PI * 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 color = vec3(0.0);
|
||||||
|
|
||||||
|
for(float i = 0.0; i < 100.0; i++) {
|
||||||
|
vec3 pos = origin + direction * depth;
|
||||||
|
pos.xz *= rotX;
|
||||||
|
|
||||||
|
// Apply vertical scaling and wave deformation
|
||||||
|
vec3 deformed = pos;
|
||||||
|
deformed.y *= uPillarHeight;
|
||||||
|
deformed = applyWaveDeformation(deformed + vec3(0.0, uTime, 0.0), uTime);
|
||||||
|
|
||||||
|
// Calculate distance field using cosine pattern
|
||||||
|
vec2 cosinePair = cos(deformed.xz);
|
||||||
|
float fieldDistance = length(cosinePair) - 0.2;
|
||||||
|
|
||||||
|
// Radial boundary constraint
|
||||||
|
float radialBound = length(pos.xz) - uPillarWidth;
|
||||||
|
fieldDistance = blendMax(radialBound, fieldDistance, 1.0);
|
||||||
|
fieldDistance = abs(fieldDistance) * 0.15 + 0.01;
|
||||||
|
|
||||||
|
vec3 gradient = mix(uBottomColor, uTopColor, smoothstep(15.0, -15.0, pos.y));
|
||||||
|
color += gradient * pow(1.0 / fieldDistance, 1.0);
|
||||||
|
|
||||||
|
if(fieldDistance < EPSILON || depth > maxDepth) break;
|
||||||
|
depth += fieldDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize by pillar width to maintain consistent glow regardless of size
|
||||||
|
float widthNormalization = uPillarWidth / 3.0;
|
||||||
|
color = tanh(color * uGlowAmount / widthNormalization);
|
||||||
|
|
||||||
|
// Add noise postprocessing
|
||||||
|
float rnd = noise(gl_FragCoord.xy);
|
||||||
|
color -= rnd / 15.0 * uNoiseIntensity;
|
||||||
|
|
||||||
|
gl_FragColor = vec4(color * uIntensity, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uResolution: { value: new THREE.Vector2(width, height) },
|
||||||
|
uMouse: { value: mouseRef.value },
|
||||||
|
uTopColor: { value: parseColor(props.topColor) },
|
||||||
|
uBottomColor: { value: parseColor(props.bottomColor) },
|
||||||
|
uIntensity: { value: props.intensity },
|
||||||
|
uInteractive: { value: props.interactive },
|
||||||
|
uGlowAmount: { value: props.glowAmount },
|
||||||
|
uPillarWidth: { value: props.pillarWidth },
|
||||||
|
uPillarHeight: { value: props.pillarHeight },
|
||||||
|
uNoiseIntensity: { value: props.noiseIntensity },
|
||||||
|
uPillarRotation: { value: props.pillarRotation }
|
||||||
|
},
|
||||||
|
transparent: true,
|
||||||
|
depthWrite: false,
|
||||||
|
depthTest: false
|
||||||
|
});
|
||||||
|
materialRef.value = material;
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneGeometry(2, 2);
|
||||||
|
geometryRef.value = geometry;
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
scene.add(mesh);
|
||||||
|
|
||||||
|
// Mouse interaction - throttled for performance
|
||||||
|
let mouseMoveTimeout: number | null = null;
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
if (!props.interactive) return;
|
||||||
|
|
||||||
|
if (mouseMoveTimeout) return;
|
||||||
|
|
||||||
|
mouseMoveTimeout = window.setTimeout(() => {
|
||||||
|
mouseMoveTimeout = null;
|
||||||
|
}, 16); // ~60fps throttle
|
||||||
|
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||||
|
const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||||
|
mouseRef.value.set(x, y);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.interactive) {
|
||||||
|
container.addEventListener('mousemove', handleMouseMove, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation loop with fixed timestep
|
||||||
|
let lastTime = performance.now();
|
||||||
|
const targetFPS = 60;
|
||||||
|
const frameTime = 1000 / targetFPS;
|
||||||
|
|
||||||
|
const animate = (currentTime: number) => {
|
||||||
|
if (!materialRef.value || !rendererRef.value || !sceneRef.value || !cameraRef.value) return;
|
||||||
|
|
||||||
|
const deltaTime = currentTime - lastTime;
|
||||||
|
|
||||||
|
if (deltaTime >= frameTime) {
|
||||||
|
timeRef.value += 0.016 * props.rotationSpeed;
|
||||||
|
materialRef.value.uniforms.uTime.value = timeRef.value;
|
||||||
|
rendererRef.value.render(sceneRef.value, cameraRef.value);
|
||||||
|
lastTime = currentTime - (deltaTime % frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
rafRef.value = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
rafRef.value = requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
// Handle resize with debouncing
|
||||||
|
let resizeTimeout: number | null = null;
|
||||||
|
const handleResize = () => {
|
||||||
|
if (resizeTimeout) {
|
||||||
|
clearTimeout(resizeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeTimeout = window.setTimeout(() => {
|
||||||
|
if (!rendererRef.value || !materialRef.value || !containerRef.value) return;
|
||||||
|
const newWidth = containerRef.value.clientWidth;
|
||||||
|
const newHeight = containerRef.value.clientHeight;
|
||||||
|
rendererRef.value.setSize(newWidth, newHeight);
|
||||||
|
materialRef.value.uniforms.uResolution.value.set(newWidth, newHeight);
|
||||||
|
}, 150);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize, { passive: true });
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanup = () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (props.interactive) {
|
||||||
|
container.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
}
|
||||||
|
if (rafRef.value) {
|
||||||
|
cancelAnimationFrame(rafRef.value);
|
||||||
|
}
|
||||||
|
if (rendererRef.value) {
|
||||||
|
rendererRef.value.dispose();
|
||||||
|
rendererRef.value.forceContextLoss();
|
||||||
|
if (container.contains(rendererRef.value.domElement)) {
|
||||||
|
container.removeChild(rendererRef.value.domElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (materialRef.value) {
|
||||||
|
materialRef.value.dispose();
|
||||||
|
}
|
||||||
|
if (geometryRef.value) {
|
||||||
|
geometryRef.value.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
rendererRef.value = null;
|
||||||
|
materialRef.value = null;
|
||||||
|
sceneRef.value = null;
|
||||||
|
cameraRef.value = null;
|
||||||
|
geometryRef.value = null;
|
||||||
|
rafRef.value = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.topColor,
|
||||||
|
props.bottomColor,
|
||||||
|
props.intensity,
|
||||||
|
props.rotationSpeed,
|
||||||
|
props.interactive,
|
||||||
|
props.glowAmount,
|
||||||
|
props.pillarWidth,
|
||||||
|
props.pillarHeight,
|
||||||
|
props.noiseIntensity,
|
||||||
|
props.pillarRotation,
|
||||||
|
webGLSupported.value
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="!webGLSupported"
|
||||||
|
:class="`w-full h-full absolute top-0 left-0 flex items-center justify-center bg-black/10 text-gray-500 text-sm ${className}`"
|
||||||
|
:style="{ mixBlendMode }"
|
||||||
|
>
|
||||||
|
WebGL not supported
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
ref="containerRef"
|
||||||
|
:class="`w-full h-full absolute top-0 left-0 ${className}`"
|
||||||
|
:style="{ mixBlendMode }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
161
src/demo/Backgrounds/LightPillarDemo.vue
Normal file
161
src/demo/Backgrounds/LightPillarDemo.vue
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<LightPillar
|
||||||
|
:top-color="topColor"
|
||||||
|
:bottom-color="bottomColor"
|
||||||
|
:intensity="intensity"
|
||||||
|
:rotation-speed="rotationSpeed"
|
||||||
|
:interactive="interactive"
|
||||||
|
:glow-amount="glowAmount"
|
||||||
|
:pillar-width="pillarWidth"
|
||||||
|
:pillar-height="pillarHeight"
|
||||||
|
:noise-intensity="noiseIntensity"
|
||||||
|
:pillar-rotation="pillarRotation"
|
||||||
|
:mix-blend-mode="mixBlendMode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="Ethereal light pillar for your hero sections." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewColor v-model="topColor" title="Top Color" />
|
||||||
|
<PreviewColor v-model="bottomColor" title="Bottom Color" class="mt-4" />
|
||||||
|
<PreviewSlider :min="0.1" :max="3" :step="0.1" v-model="intensity" title="Intensity" />
|
||||||
|
<PreviewSlider :min="0" :max="2" :step="0.1" v-model="rotationSpeed" title="Rotation Speed" />
|
||||||
|
<PreviewSlider :min="0.001" :max="0.02" :step="0.001" v-model="glowAmount" title="Glow Amount" />
|
||||||
|
<PreviewSlider :min="1" :max="10" :step="0.1" v-model="pillarWidth" title="Pillar Width" />
|
||||||
|
<PreviewSlider :min="0.1" :max="2" :step="0.1" v-model="pillarHeight" title="Pillar Height" />
|
||||||
|
<PreviewSlider :min="0" :max="2" :step="0.1" v-model="noiseIntensity" title="Noise Intensity" />
|
||||||
|
<PreviewSlider :min="0" :max="360" :step="1" v-model="pillarRotation" title="Pillar Rotation" />
|
||||||
|
<PreviewSwitch title="Interactive Rotation" v-model="interactive" />
|
||||||
|
<PreviewSelect :options="blendModeOptions" v-model="mixBlendMode" title="Mix Blend Mode" />
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['three']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="lightPillar" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="lightPillar.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { lightPillar } from '@/constants/code/Backgrounds/lightPillarCode';
|
||||||
|
import { ref, type CSSProperties } 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 PreviewSlider from '../../components/common/PreviewSlider.vue';
|
||||||
|
import PreviewColor from '../../components/common/PreviewColor.vue';
|
||||||
|
import PreviewSwitch from '../../components/common/PreviewSwitch.vue';
|
||||||
|
import PreviewSelect from '../../components/common/PreviewSelect.vue';
|
||||||
|
import PropTable from '../../components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '../../components/common/TabbedLayout.vue';
|
||||||
|
import LightPillar from '../../content/Backgrounds/LightPillar/LightPillar.vue';
|
||||||
|
|
||||||
|
const topColor = ref('#48FF28');
|
||||||
|
const bottomColor = ref('#9EF19E');
|
||||||
|
const intensity = ref(1.0);
|
||||||
|
const rotationSpeed = ref(0.3);
|
||||||
|
const interactive = ref(false);
|
||||||
|
const glowAmount = ref(0.002);
|
||||||
|
const pillarWidth = ref(3.0);
|
||||||
|
const pillarHeight = ref(0.4);
|
||||||
|
const noiseIntensity = ref(0.5);
|
||||||
|
const pillarRotation = ref(25);
|
||||||
|
const mixBlendMode = ref<CSSProperties['mixBlendMode']>('screen');
|
||||||
|
|
||||||
|
const blendModeOptions = [
|
||||||
|
{ value: 'normal', label: 'Normal' },
|
||||||
|
{ value: 'screen', label: 'Screen' },
|
||||||
|
{ value: 'darken', label: 'Darken' },
|
||||||
|
{ value: 'lighten', label: 'Lighten' },
|
||||||
|
{ value: 'color-dodge', label: 'Color Dodge' },
|
||||||
|
{ value: 'luminosity', label: 'Luminosity' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'topColor',
|
||||||
|
type: 'string',
|
||||||
|
default: "'#48FF28'",
|
||||||
|
description: 'Hex color string for the top gradient color of the light pillar.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bottomColor',
|
||||||
|
type: 'string',
|
||||||
|
default: "'#9EF19E'",
|
||||||
|
description: 'Hex color string for the bottom gradient color of the light pillar.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'intensity',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Controls the overall brightness and intensity of the effect.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rotationSpeed',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.3',
|
||||||
|
description: 'Speed multiplier for the pillar rotation animation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'interactive',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Enable mouse interaction to control the pillar rotation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glowAmount',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.005',
|
||||||
|
description: 'Controls the glow intensity and spread of the light effect.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pillarWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '3.0',
|
||||||
|
description: 'Width/radius of the light pillar.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pillarHeight',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.4',
|
||||||
|
description: 'Height scaling factor for the pillar distortion.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noiseIntensity',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'Intensity of the film grain noise postprocessing effect.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'className',
|
||||||
|
type: 'string',
|
||||||
|
default: "''",
|
||||||
|
description: 'Additional CSS class names to apply to the container element.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mixBlendMode',
|
||||||
|
type: 'string',
|
||||||
|
default: "'screen'",
|
||||||
|
description: 'CSS mix-blend-mode property to control how the component blends with its background.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pillarRotation',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Rotation angle of the pillar in degrees (0-360).'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user