mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Added <GradientBlinds /> Background
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Highlighted sidebar items
|
// Highlighted sidebar items
|
||||||
export const NEW = ['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', '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 UPDATED = [];
|
export const UPDATED = [];
|
||||||
|
|
||||||
// Used for main sidebar navigation
|
// Used for main sidebar navigation
|
||||||
@@ -101,6 +101,7 @@ export const CATEGORIES = [
|
|||||||
'Beams',
|
'Beams',
|
||||||
'Dark Veil',
|
'Dark Veil',
|
||||||
'Dither',
|
'Dither',
|
||||||
|
'Gradient Blinds',
|
||||||
'Dot Grid',
|
'Dot Grid',
|
||||||
'Hyperspeed',
|
'Hyperspeed',
|
||||||
'Faulty Terminal',
|
'Faulty Terminal',
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ const backgrounds = {
|
|||||||
'light-rays': () => import('../demo/Backgrounds/LightRaysDemo.vue'),
|
'light-rays': () => import('../demo/Backgrounds/LightRaysDemo.vue'),
|
||||||
'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'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
|
|||||||
28
src/constants/code/Backgrounds/gradientBlindsCode.ts
Normal file
28
src/constants/code/Backgrounds/gradientBlindsCode.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import code from '@content/Backgrounds/GradientBlinds/GradientBlinds.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const gradientBlinds = createCodeObject(code, 'Backgrounds/GradientBlinds', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<GradientBlinds
|
||||||
|
:gradient-colors="['#1EA03F', '#182FFF']"
|
||||||
|
:angle="0"
|
||||||
|
:noise="0.3"
|
||||||
|
:blind-count="12"
|
||||||
|
:blind-min-width="50"
|
||||||
|
:spotlight-radius="0.5"
|
||||||
|
:spotlight-softness="1"
|
||||||
|
:spotlight-opacity="1"
|
||||||
|
:mouse-dampening="0.15"
|
||||||
|
:distort-amount="0"
|
||||||
|
shine-direction="left"
|
||||||
|
mix-blend-mode="lighten"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import GradientBlinds from "./GradientBlinds.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
389
src/content/Backgrounds/GradientBlinds/GradientBlinds.vue
Normal file
389
src/content/Backgrounds/GradientBlinds/GradientBlinds.vue
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Renderer, Program, Mesh, Triangle } from 'ogl';
|
||||||
|
import { onBeforeUnmount, onMounted, ref, useTemplateRef, watch, type CSSProperties } from 'vue';
|
||||||
|
|
||||||
|
export interface GradientBlindsProps {
|
||||||
|
className?: string;
|
||||||
|
dpr?: number;
|
||||||
|
paused?: boolean;
|
||||||
|
gradientColors?: string[];
|
||||||
|
angle?: number;
|
||||||
|
noise?: number;
|
||||||
|
blindCount?: number;
|
||||||
|
blindMinWidth?: number;
|
||||||
|
mouseDampening?: number;
|
||||||
|
mirrorGradient?: boolean;
|
||||||
|
spotlightRadius?: number;
|
||||||
|
spotlightSoftness?: number;
|
||||||
|
spotlightOpacity?: number;
|
||||||
|
distortAmount?: number;
|
||||||
|
shineDirection?: 'left' | 'right';
|
||||||
|
mixBlendMode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_COLORS = 8;
|
||||||
|
|
||||||
|
const hexToRGB = (hex: string): [number, number, number] => {
|
||||||
|
const c = hex.replace('#', '').padEnd(6, '0');
|
||||||
|
const r = parseInt(c.slice(0, 2), 16) / 255;
|
||||||
|
const g = parseInt(c.slice(2, 4), 16) / 255;
|
||||||
|
const b = parseInt(c.slice(4, 6), 16) / 255;
|
||||||
|
return [r, g, b];
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepStops = (stops?: string[]) => {
|
||||||
|
const base = (stops && stops.length ? stops : ['#FF9FFC', '#5227FF']).slice(0, MAX_COLORS);
|
||||||
|
if (base.length === 1) base.push(base[0]);
|
||||||
|
while (base.length < MAX_COLORS) base.push(base[base.length - 1]);
|
||||||
|
const arr: [number, number, number][] = [];
|
||||||
|
for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[i]));
|
||||||
|
const count = Math.max(2, Math.min(MAX_COLORS, stops?.length ?? 2));
|
||||||
|
return { arr, count };
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<GradientBlindsProps>(), {
|
||||||
|
paused: false,
|
||||||
|
angle: 0,
|
||||||
|
noise: 0.3,
|
||||||
|
blindCount: 16,
|
||||||
|
blindMinWidth: 60,
|
||||||
|
mouseDampening: 0.15,
|
||||||
|
mirrorGradient: false,
|
||||||
|
spotlightRadius: 0.5,
|
||||||
|
spotlightSoftness: 1,
|
||||||
|
spotlightOpacity: 1,
|
||||||
|
distortAmount: 0,
|
||||||
|
shineDirection: 'left',
|
||||||
|
mixBlendMode: 'lighten'
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef('containerRef');
|
||||||
|
const rafRef = ref<number>(0);
|
||||||
|
const programRef = ref<Program | null>(null);
|
||||||
|
const meshRef = ref<Mesh<Triangle> | null>(null);
|
||||||
|
const geometryRef = ref<Triangle | null>(null);
|
||||||
|
const rendererRef = ref<Renderer | null>(null);
|
||||||
|
const mouseTargetRef = ref<[number, number]>([0, 0]);
|
||||||
|
const lastTimeRef = ref<number>(0);
|
||||||
|
const firstResizeRef = ref<boolean>(true);
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const container = containerRef.value;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const renderer = new Renderer({
|
||||||
|
dpr: props.dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),
|
||||||
|
alpha: true,
|
||||||
|
antialias: true
|
||||||
|
});
|
||||||
|
rendererRef.value = renderer;
|
||||||
|
const gl = renderer.gl;
|
||||||
|
const canvas = gl.canvas as HTMLCanvasElement;
|
||||||
|
|
||||||
|
canvas.style.width = '100%';
|
||||||
|
canvas.style.height = '100%';
|
||||||
|
canvas.style.display = 'block';
|
||||||
|
container.appendChild(canvas);
|
||||||
|
|
||||||
|
const vertex = `
|
||||||
|
attribute vec2 position;
|
||||||
|
attribute vec2 uv;
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragment = `
|
||||||
|
#ifdef GL_ES
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uniform vec3 iResolution;
|
||||||
|
uniform vec2 iMouse;
|
||||||
|
uniform float iTime;
|
||||||
|
|
||||||
|
uniform float uAngle;
|
||||||
|
uniform float uNoise;
|
||||||
|
uniform float uBlindCount;
|
||||||
|
uniform float uSpotlightRadius;
|
||||||
|
uniform float uSpotlightSoftness;
|
||||||
|
uniform float uSpotlightOpacity;
|
||||||
|
uniform float uMirror;
|
||||||
|
uniform float uDistort;
|
||||||
|
uniform float uShineFlip;
|
||||||
|
uniform vec3 uColor0;
|
||||||
|
uniform vec3 uColor1;
|
||||||
|
uniform vec3 uColor2;
|
||||||
|
uniform vec3 uColor3;
|
||||||
|
uniform vec3 uColor4;
|
||||||
|
uniform vec3 uColor5;
|
||||||
|
uniform vec3 uColor6;
|
||||||
|
uniform vec3 uColor7;
|
||||||
|
uniform int uColorCount;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
float rand(vec2 co){
|
||||||
|
return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 rotate2D(vec2 p, float a){
|
||||||
|
float c = cos(a);
|
||||||
|
float s = sin(a);
|
||||||
|
return mat2(c, -s, s, c) * p;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 getGradientColor(float t){
|
||||||
|
float tt = clamp(t, 0.0, 1.0);
|
||||||
|
int count = uColorCount;
|
||||||
|
if (count < 2) count = 2;
|
||||||
|
float scaled = tt * float(count - 1);
|
||||||
|
float seg = floor(scaled);
|
||||||
|
float f = fract(scaled);
|
||||||
|
|
||||||
|
if (seg < 1.0) return mix(uColor0, uColor1, f);
|
||||||
|
if (seg < 2.0 && count > 2) return mix(uColor1, uColor2, f);
|
||||||
|
if (seg < 3.0 && count > 3) return mix(uColor2, uColor3, f);
|
||||||
|
if (seg < 4.0 && count > 4) return mix(uColor3, uColor4, f);
|
||||||
|
if (seg < 5.0 && count > 5) return mix(uColor4, uColor5, f);
|
||||||
|
if (seg < 6.0 && count > 6) return mix(uColor5, uColor6, f);
|
||||||
|
if (seg < 7.0 && count > 7) return mix(uColor6, uColor7, f);
|
||||||
|
if (count > 7) return uColor7;
|
||||||
|
if (count > 6) return uColor6;
|
||||||
|
if (count > 5) return uColor5;
|
||||||
|
if (count > 4) return uColor4;
|
||||||
|
if (count > 3) return uColor3;
|
||||||
|
if (count > 2) return uColor2;
|
||||||
|
return uColor1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainImage( out vec4 fragColor, in vec2 fragCoord )
|
||||||
|
{
|
||||||
|
vec2 uv0 = fragCoord.xy / iResolution.xy;
|
||||||
|
|
||||||
|
float aspect = iResolution.x / iResolution.y;
|
||||||
|
vec2 p = uv0 * 2.0 - 1.0;
|
||||||
|
p.x *= aspect;
|
||||||
|
vec2 pr = rotate2D(p, uAngle);
|
||||||
|
pr.x /= aspect;
|
||||||
|
vec2 uv = pr * 0.5 + 0.5;
|
||||||
|
|
||||||
|
vec2 uvMod = uv;
|
||||||
|
if (uDistort > 0.0) {
|
||||||
|
float a = uvMod.y * 6.0;
|
||||||
|
float b = uvMod.x * 6.0;
|
||||||
|
float w = 0.01 * uDistort;
|
||||||
|
uvMod.x += sin(a) * w;
|
||||||
|
uvMod.y += cos(b) * w;
|
||||||
|
}
|
||||||
|
float t = uvMod.x;
|
||||||
|
if (uMirror > 0.5) {
|
||||||
|
t = 1.0 - abs(1.0 - 2.0 * fract(t));
|
||||||
|
}
|
||||||
|
vec3 base = getGradientColor(t);
|
||||||
|
|
||||||
|
vec2 offset = vec2(iMouse.x/iResolution.x, iMouse.y/iResolution.y);
|
||||||
|
float d = length(uv0 - offset);
|
||||||
|
float r = max(uSpotlightRadius, 1e-4);
|
||||||
|
float dn = d / r;
|
||||||
|
float spot = (1.0 - 2.0 * pow(dn, uSpotlightSoftness)) * uSpotlightOpacity;
|
||||||
|
vec3 cir = vec3(spot);
|
||||||
|
float stripe = fract(uvMod.x * max(uBlindCount, 1.0));
|
||||||
|
if (uShineFlip > 0.5) stripe = 1.0 - stripe;
|
||||||
|
vec3 ran = vec3(stripe);
|
||||||
|
|
||||||
|
vec3 col = cir + base - ran;
|
||||||
|
col += (rand(gl_FragCoord.xy + iTime) - 0.5) * uNoise;
|
||||||
|
|
||||||
|
fragColor = vec4(col, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color;
|
||||||
|
mainImage(color, vUv * iResolution.xy);
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { arr: colorArr, count: colorCount } = prepStops(props.gradientColors);
|
||||||
|
const uniforms: {
|
||||||
|
iResolution: { value: [number, number, number] };
|
||||||
|
iMouse: { value: [number, number] };
|
||||||
|
iTime: { value: number };
|
||||||
|
uAngle: { value: number };
|
||||||
|
uNoise: { value: number };
|
||||||
|
uBlindCount: { value: number };
|
||||||
|
uSpotlightRadius: { value: number };
|
||||||
|
uSpotlightSoftness: { value: number };
|
||||||
|
uSpotlightOpacity: { value: number };
|
||||||
|
uMirror: { value: number };
|
||||||
|
uDistort: { value: number };
|
||||||
|
uShineFlip: { value: number };
|
||||||
|
uColor0: { value: [number, number, number] };
|
||||||
|
uColor1: { value: [number, number, number] };
|
||||||
|
uColor2: { value: [number, number, number] };
|
||||||
|
uColor3: { value: [number, number, number] };
|
||||||
|
uColor4: { value: [number, number, number] };
|
||||||
|
uColor5: { value: [number, number, number] };
|
||||||
|
uColor6: { value: [number, number, number] };
|
||||||
|
uColor7: { value: [number, number, number] };
|
||||||
|
uColorCount: { value: number };
|
||||||
|
} = {
|
||||||
|
iResolution: {
|
||||||
|
value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1]
|
||||||
|
},
|
||||||
|
iMouse: { value: [0, 0] },
|
||||||
|
iTime: { value: 0 },
|
||||||
|
uAngle: { value: (props.angle * Math.PI) / 180 },
|
||||||
|
uNoise: { value: props.noise },
|
||||||
|
uBlindCount: { value: Math.max(1, props.blindCount) },
|
||||||
|
uSpotlightRadius: { value: props.spotlightRadius },
|
||||||
|
uSpotlightSoftness: { value: props.spotlightSoftness },
|
||||||
|
uSpotlightOpacity: { value: props.spotlightOpacity },
|
||||||
|
uMirror: { value: props.mirrorGradient ? 1 : 0 },
|
||||||
|
uDistort: { value: props.distortAmount },
|
||||||
|
uShineFlip: { value: props.shineDirection === 'right' ? 1 : 0 },
|
||||||
|
uColor0: { value: colorArr[0] },
|
||||||
|
uColor1: { value: colorArr[1] },
|
||||||
|
uColor2: { value: colorArr[2] },
|
||||||
|
uColor3: { value: colorArr[3] },
|
||||||
|
uColor4: { value: colorArr[4] },
|
||||||
|
uColor5: { value: colorArr[5] },
|
||||||
|
uColor6: { value: colorArr[6] },
|
||||||
|
uColor7: { value: colorArr[7] },
|
||||||
|
uColorCount: { value: colorCount }
|
||||||
|
};
|
||||||
|
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex,
|
||||||
|
fragment,
|
||||||
|
uniforms
|
||||||
|
});
|
||||||
|
programRef.value = program;
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
geometryRef.value = geometry;
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
meshRef.value = mesh;
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
renderer.setSize(rect.width, rect.height);
|
||||||
|
uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];
|
||||||
|
|
||||||
|
if (props.blindMinWidth && props.blindMinWidth > 0) {
|
||||||
|
const maxByMinWidth = Math.max(1, Math.floor(rect.width / props.blindMinWidth));
|
||||||
|
|
||||||
|
const effective = props.blindCount ? Math.min(props.blindCount, maxByMinWidth) : maxByMinWidth;
|
||||||
|
uniforms.uBlindCount.value = Math.max(1, effective);
|
||||||
|
} else {
|
||||||
|
uniforms.uBlindCount.value = Math.max(1, props.blindCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstResizeRef.value) {
|
||||||
|
firstResizeRef.value = false;
|
||||||
|
const cx = gl.drawingBufferWidth / 2;
|
||||||
|
const cy = gl.drawingBufferHeight / 2;
|
||||||
|
uniforms.iMouse.value = [cx, cy];
|
||||||
|
mouseTargetRef.value = [cx, cy];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resize();
|
||||||
|
const ro = new ResizeObserver(resize);
|
||||||
|
ro.observe(container);
|
||||||
|
|
||||||
|
const onPointerMove = (e: PointerEvent) => {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const scale = (renderer as unknown as { dpr?: number }).dpr || 1;
|
||||||
|
const x = (e.clientX - rect.left) * scale;
|
||||||
|
const y = (rect.height - (e.clientY - rect.top)) * scale;
|
||||||
|
mouseTargetRef.value = [x, y];
|
||||||
|
if (props.mouseDampening <= 0) {
|
||||||
|
uniforms.iMouse.value = [x, y];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
canvas.addEventListener('pointermove', onPointerMove);
|
||||||
|
|
||||||
|
const loop = (t: number) => {
|
||||||
|
rafRef.value = requestAnimationFrame(loop);
|
||||||
|
uniforms.iTime.value = t * 0.001;
|
||||||
|
if (props.mouseDampening > 0) {
|
||||||
|
if (!lastTimeRef.value) lastTimeRef.value = t;
|
||||||
|
const dt = (t - lastTimeRef.value) / 1000;
|
||||||
|
lastTimeRef.value = t;
|
||||||
|
const tau = Math.max(1e-4, props.mouseDampening);
|
||||||
|
let factor = 1 - Math.exp(-dt / tau);
|
||||||
|
if (factor > 1) factor = 1;
|
||||||
|
const target = mouseTargetRef.value;
|
||||||
|
const cur = uniforms.iMouse.value;
|
||||||
|
cur[0] += (target[0] - cur[0]) * factor;
|
||||||
|
cur[1] += (target[1] - cur[1]) * factor;
|
||||||
|
} else {
|
||||||
|
lastTimeRef.value = t;
|
||||||
|
}
|
||||||
|
if (!props.paused && programRef.value && meshRef.value) {
|
||||||
|
try {
|
||||||
|
renderer.render({ scene: meshRef.value });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rafRef.value = requestAnimationFrame(loop);
|
||||||
|
|
||||||
|
cleanup = () => {
|
||||||
|
if (rafRef.value) cancelAnimationFrame(rafRef.value);
|
||||||
|
canvas.removeEventListener('pointermove', onPointerMove);
|
||||||
|
ro.disconnect();
|
||||||
|
if (canvas.parentElement === container) {
|
||||||
|
container.removeChild(canvas);
|
||||||
|
}
|
||||||
|
const callIfFn = <T extends object, K extends keyof T>(obj: T | null, key: K) => {
|
||||||
|
if (obj && typeof obj[key] === 'function') {
|
||||||
|
(obj[key] as unknown as () => void).call(obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
callIfFn(programRef.value, 'remove');
|
||||||
|
callIfFn(geometryRef.value, 'remove');
|
||||||
|
callIfFn(meshRef.value as unknown as { remove?: () => void }, 'remove');
|
||||||
|
callIfFn(rendererRef.value as unknown as { destroy?: () => void }, 'destroy');
|
||||||
|
programRef.value = null;
|
||||||
|
geometryRef.value = null;
|
||||||
|
meshRef.value = null;
|
||||||
|
rendererRef.value = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
props,
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="containerRef"
|
||||||
|
:class="['w-full h-full overflow-hidden relative', className]"
|
||||||
|
:style="{
|
||||||
|
...(mixBlendMode ? { mixBlendMode: mixBlendMode as CSSProperties['mixBlendMode'] } : {})
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
187
src/demo/Backgrounds/GradientBlindsDemo.vue
Normal file
187
src/demo/Backgrounds/GradientBlindsDemo.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<GradientBlinds
|
||||||
|
:gradient-colors="gradientColors"
|
||||||
|
:angle="angle"
|
||||||
|
:noise="noise"
|
||||||
|
:blind-count="blindCount"
|
||||||
|
:blind-min-width="blindMinWidth"
|
||||||
|
:spotlight-radius="spotlightRadius"
|
||||||
|
:distort-amount="distortAmount"
|
||||||
|
:mouse-dampening="mouseDampening"
|
||||||
|
:shine-direction="shineDirection"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="Smooth gradients make everything better" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewColor title="Color 1" v-model="color1" />
|
||||||
|
<PreviewColor title="Color 2" v-model="color2" />
|
||||||
|
<PreviewSelect title="Light Direction" v-model="shineDirection" :options="directionOptions" />
|
||||||
|
<PreviewSlider title="Blinds Angle" :min="0" :max="360" :step="1" v-model="angle" />
|
||||||
|
<PreviewSlider title="Noise Amount" :min="0" :max="1" :step="0.01" v-model="noise" />
|
||||||
|
<PreviewSlider title="Blinds Count" :min="1" :max="64" :step="1" v-model="blindCount" />
|
||||||
|
<PreviewSlider title="Min Blind W" :min="10" :max="200" :step="5" v-model="blindMinWidth" />
|
||||||
|
<PreviewSlider title="Spot Radius" :min="0.05" :max="1" :step="0.05" v-model="spotlightRadius" />
|
||||||
|
<PreviewSlider title="Distort" :min="0" :max="100" :step="1" v-model="distortAmount" />
|
||||||
|
<PreviewSlider title="Mouse Damp" :min="0" :max="1" :step="0.01" v-model="mouseDampening" />
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="gradientBlinds" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="gradientBlinds.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { gradientBlinds } from '@/constants/code/Backgrounds/gradientBlindsCode';
|
||||||
|
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 GradientBlinds from '../../content/Backgrounds/GradientBlinds/GradientBlinds.vue';
|
||||||
|
|
||||||
|
type DirectionKey = 'left' | 'right';
|
||||||
|
|
||||||
|
const directionOptions = [
|
||||||
|
{ label: 'Left', value: 'left' },
|
||||||
|
{ label: 'Right', value: 'right' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const color1 = ref('#1EA03F');
|
||||||
|
const color2 = ref('#182FFF');
|
||||||
|
const angle = ref(20);
|
||||||
|
const noise = ref(0.5);
|
||||||
|
const blindCount = ref(16);
|
||||||
|
const blindMinWidth = ref(60);
|
||||||
|
const spotlightRadius = ref(0.5);
|
||||||
|
const distortAmount = ref(0);
|
||||||
|
const mouseDampening = ref(0.15);
|
||||||
|
const shineDirection = ref<DirectionKey>('left');
|
||||||
|
|
||||||
|
const gradientColors = computed(() => [color1.value, color2.value]);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'gradientColors',
|
||||||
|
type: 'string[]',
|
||||||
|
default: "['#1EA03F', '#182FFF']",
|
||||||
|
description:
|
||||||
|
'Array of hex colors (up to 8) forming the animated gradient. If one color is provided it is duplicated.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'angle',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Rotation of the gradient in degrees (0 = horizontal left→right).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noise',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.3',
|
||||||
|
description: 'Strength of per‑pixel noise added to the final color (0 = clean).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blindCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '16',
|
||||||
|
description: 'Target number of vertical blinds. Acts as an upper bound when blindMinWidth is set.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blindMinWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '60',
|
||||||
|
description: 'Minimum pixel width for each blind. Reduces effective blindCount if necessary to satisfy this width.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mouseDampening',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.15',
|
||||||
|
description: 'Easing time constant (seconds) for the spotlight to follow the cursor. 0 = immediate.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mirrorGradient',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Creates a mirrored ping‑pong gradient progression instead of a linear wrap.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spotlightRadius',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'Normalized spotlight radius relative to the shorter canvas dimension.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spotlightSoftness',
|
||||||
|
type: 'number',
|
||||||
|
default: '1',
|
||||||
|
description: 'Falloff exponent for spotlight edge. Higher = sharper edge (values >1 increase contrast).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spotlightOpacity',
|
||||||
|
type: 'number',
|
||||||
|
default: '1',
|
||||||
|
description: 'Overall intensity multiplier for the spotlight highlight.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'distortAmount',
|
||||||
|
type: 'number',
|
||||||
|
default: '0',
|
||||||
|
description: 'Sin/cos warp intensity applied to UVs for subtle wavy distortion.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shineDirection',
|
||||||
|
type: "'left' | 'right'",
|
||||||
|
default: 'left',
|
||||||
|
description: 'Flips the bright side of each blind; useful for composition with other elements.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mixBlendMode',
|
||||||
|
type: 'string',
|
||||||
|
default: "'lighten'",
|
||||||
|
description: "CSS mix-blend-mode applied to the canvas (e.g. 'screen', 'overlay', 'multiply')."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'paused',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'If true, stops rendering updates (freezing the current frame).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dpr',
|
||||||
|
type: 'number',
|
||||||
|
default: 'window.devicePixelRatio',
|
||||||
|
description: 'Overrides device pixel ratio; lower for performance, higher for sharpness.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'className',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Additional class names for the root container.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user