mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-04-21 17:44:39 -06:00
feat: added <Radar /> background
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import code from '@content/Backgrounds/Radar/Radar.vue?raw';
|
||||||
|
import { createCodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const radar = createCodeObject(code, 'Backgrounds/Radar', {
|
||||||
|
installation: `npm install ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<div style="width: 100%; height: 600px; position: relative;">
|
||||||
|
<Radar
|
||||||
|
:speed="1"
|
||||||
|
:scale="0.5"
|
||||||
|
:ringCount="10"
|
||||||
|
:spokeCount="10"
|
||||||
|
:ringThickness="0.05"
|
||||||
|
:spokeThickness="0.01"
|
||||||
|
:sweepSpeed="1"
|
||||||
|
:sweepWidth="2"
|
||||||
|
:sweepLobes="1"
|
||||||
|
color="#9f29ff"
|
||||||
|
backgroundColor="#000000"
|
||||||
|
:falloff="2"
|
||||||
|
:brightness="1"
|
||||||
|
:enableMouseInteraction="true"
|
||||||
|
:mouseInfluence="0.1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Radar from "./Radar.vue";
|
||||||
|
</script>`
|
||||||
|
});
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Renderer, Program, Mesh, Triangle } from 'ogl';
|
||||||
|
import { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface RadarProps {
|
||||||
|
speed?: number;
|
||||||
|
scale?: number;
|
||||||
|
ringCount?: number;
|
||||||
|
spokeCount?: number;
|
||||||
|
ringThickness?: number;
|
||||||
|
spokeThickness?: number;
|
||||||
|
sweepSpeed?: number;
|
||||||
|
sweepWidth?: number;
|
||||||
|
sweepLobes?: number;
|
||||||
|
color?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
falloff?: number;
|
||||||
|
brightness?: number;
|
||||||
|
enableMouseInteraction?: boolean;
|
||||||
|
mouseInfluence?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToVec3(hex: string): [number, number, number] {
|
||||||
|
const h = hex.replace('#', '');
|
||||||
|
return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec2 position;
|
||||||
|
varying vec2 vUv;
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 0, 1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec3 uResolution;
|
||||||
|
uniform float uSpeed;
|
||||||
|
uniform float uScale;
|
||||||
|
uniform float uRingCount;
|
||||||
|
uniform float uSpokeCount;
|
||||||
|
uniform float uRingThickness;
|
||||||
|
uniform float uSpokeThickness;
|
||||||
|
uniform float uSweepSpeed;
|
||||||
|
uniform float uSweepWidth;
|
||||||
|
uniform float uSweepLobes;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform vec3 uBgColor;
|
||||||
|
uniform float uFalloff;
|
||||||
|
uniform float uBrightness;
|
||||||
|
uniform vec2 uMouse;
|
||||||
|
uniform float uMouseInfluence;
|
||||||
|
uniform bool uEnableMouse;
|
||||||
|
|
||||||
|
#define TAU 6.28318530718
|
||||||
|
#define PI 3.14159265359
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 st = gl_FragCoord.xy / uResolution.xy;
|
||||||
|
st = st * 2.0 - 1.0;
|
||||||
|
st.x *= uResolution.x / uResolution.y;
|
||||||
|
|
||||||
|
if (uEnableMouse) {
|
||||||
|
vec2 mShift = (uMouse * 2.0 - 1.0);
|
||||||
|
mShift.x *= uResolution.x / uResolution.y;
|
||||||
|
st -= mShift * uMouseInfluence;
|
||||||
|
}
|
||||||
|
|
||||||
|
st *= uScale;
|
||||||
|
|
||||||
|
float dist = length(st);
|
||||||
|
float theta = atan(st.y, st.x);
|
||||||
|
float t = uTime * uSpeed;
|
||||||
|
|
||||||
|
float ringPhase = dist * uRingCount - t;
|
||||||
|
float ringDist = abs(fract(ringPhase) - 0.5);
|
||||||
|
float ringGlow = 1.0 - smoothstep(0.0, uRingThickness, ringDist);
|
||||||
|
|
||||||
|
float spokeAngle = abs(fract(theta * uSpokeCount / TAU + 0.5) - 0.5) * TAU / uSpokeCount;
|
||||||
|
float arcDist = spokeAngle * dist;
|
||||||
|
float spokeGlow = (1.0 - smoothstep(0.0, uSpokeThickness, arcDist)) * smoothstep(0.0, 0.1, dist);
|
||||||
|
|
||||||
|
float sweepPhase = t * uSweepSpeed;
|
||||||
|
float sweepBeam = pow(max(0.5 * sin(uSweepLobes * theta + sweepPhase) + 0.5, 0.0), uSweepWidth);
|
||||||
|
|
||||||
|
float fade = smoothstep(1.05, 0.85, dist) * pow(max(1.0 - dist, 0.0), uFalloff);
|
||||||
|
|
||||||
|
float intensity = max((ringGlow + spokeGlow + sweepBeam) * fade * uBrightness, 0.0);
|
||||||
|
vec3 col = uColor * intensity + uBgColor;
|
||||||
|
|
||||||
|
float alpha = clamp(length(col), 0.0, 1.0);
|
||||||
|
gl_FragColor = vec4(col, alpha);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<RadarProps>(), {
|
||||||
|
speed: 1.0,
|
||||||
|
scale: 0.5,
|
||||||
|
ringCount: 10.0,
|
||||||
|
spokeCount: 10.0,
|
||||||
|
ringThickness: 0.05,
|
||||||
|
spokeThickness: 0.01,
|
||||||
|
sweepSpeed: 1.0,
|
||||||
|
sweepWidth: 2.0,
|
||||||
|
sweepLobes: 1.0,
|
||||||
|
color: '#9f29ff',
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
falloff: 2.0,
|
||||||
|
brightness: 1.0,
|
||||||
|
enableMouseInteraction: true,
|
||||||
|
mouseInfluence: 0.1
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
|
||||||
|
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
const setup = () => {
|
||||||
|
if (!containerRef.value) return;
|
||||||
|
const container = containerRef.value;
|
||||||
|
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const currentMouse = [0.5, 0.5];
|
||||||
|
let targetMouse = [0.5, 0.5];
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
const rect = gl.canvas.getBoundingClientRect();
|
||||||
|
targetMouse = [(e.clientX - rect.left) / rect.width, 1.0 - (e.clientY - rect.top) / rect.height];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseLeave() {
|
||||||
|
targetMouse = [0.5, 0.5];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex: vertexShader,
|
||||||
|
fragment: fragmentShader,
|
||||||
|
uniforms: {
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
|
||||||
|
uSpeed: { value: props.speed },
|
||||||
|
uScale: { value: props.scale },
|
||||||
|
uRingCount: { value: props.ringCount },
|
||||||
|
uSpokeCount: { value: props.spokeCount },
|
||||||
|
uRingThickness: { value: props.ringThickness },
|
||||||
|
uSpokeThickness: { value: props.spokeThickness },
|
||||||
|
uSweepSpeed: { value: props.sweepSpeed },
|
||||||
|
uSweepWidth: { value: props.sweepWidth },
|
||||||
|
uSweepLobes: { value: props.sweepLobes },
|
||||||
|
uColor: { value: hexToVec3(props.color) },
|
||||||
|
uBgColor: { value: hexToVec3(props.backgroundColor) },
|
||||||
|
uFalloff: { value: props.falloff },
|
||||||
|
uBrightness: { value: props.brightness },
|
||||||
|
uMouse: { value: new Float32Array([0.5, 0.5]) },
|
||||||
|
uMouseInfluence: { value: props.mouseInfluence },
|
||||||
|
uEnableMouse: { value: props.enableMouseInteraction }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
renderer.setSize(container.offsetWidth, container.offsetHeight);
|
||||||
|
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.addEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
program.uniforms.uTime.value = time * 0.001;
|
||||||
|
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
|
||||||
|
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
|
||||||
|
program.uniforms.uMouse.value[0] = currentMouse[0];
|
||||||
|
program.uniforms.uMouse.value[1] = currentMouse[1];
|
||||||
|
} else {
|
||||||
|
program.uniforms.uMouse.value[0] = 0.5;
|
||||||
|
program.uniforms.uMouse.value[1] = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.render({ scene: mesh });
|
||||||
|
}
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
cleanup = () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
if (props.enableMouseInteraction) {
|
||||||
|
gl.canvas.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
|
}
|
||||||
|
container.removeChild(gl.canvas);
|
||||||
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
cleanup?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.speed,
|
||||||
|
props.scale,
|
||||||
|
props.ringCount,
|
||||||
|
props.spokeCount,
|
||||||
|
props.ringThickness,
|
||||||
|
props.spokeThickness,
|
||||||
|
props.sweepSpeed,
|
||||||
|
props.sweepWidth,
|
||||||
|
props.sweepLobes,
|
||||||
|
props.color,
|
||||||
|
props.backgroundColor,
|
||||||
|
props.falloff,
|
||||||
|
props.brightness,
|
||||||
|
props.enableMouseInteraction,
|
||||||
|
props.mouseInfluence
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
cleanup?.();
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="w-full h-full" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative p-0 h-[600px] overflow-hidden demo-container">
|
||||||
|
<Radar
|
||||||
|
:speed="speed"
|
||||||
|
:scale="scale"
|
||||||
|
:ring-count="ringCount"
|
||||||
|
:spoke-count="spokeCount"
|
||||||
|
:ring-thickness="ringThickness"
|
||||||
|
:spoke-thickness="spokeThickness"
|
||||||
|
:sweep-speed="sweepSpeed"
|
||||||
|
:sweep-width="sweepWidth"
|
||||||
|
:sweep-lobes="sweepLobes"
|
||||||
|
:color="color"
|
||||||
|
:background-color="backgroundColor"
|
||||||
|
:falloff="falloff"
|
||||||
|
:brightness="brightness"
|
||||||
|
:enable-mouse-interaction="enableMouseInteraction"
|
||||||
|
:mouse-influence="mouseInfluence"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BackgroundContent pill-text="New Background" headline="Scan the horizon of your imagination!" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<div class="flex items-center gap-4 mt-4">
|
||||||
|
<PreviewColor title="Color" v-model="color" />
|
||||||
|
<PreviewColor title="Background" v-model="backgroundColor" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PreviewSlider title="Speed" :min="0.1" :max="5" :step="0.1" v-model="speed" />
|
||||||
|
<PreviewSlider title="Scale" :min="0.1" :max="3" :step="0.1" v-model="scale" />
|
||||||
|
<PreviewSlider title="Ring Count" :min="1" :max="30" :step="1" v-model="ringCount" />
|
||||||
|
<PreviewSlider title="Spoke Count" :min="1" :max="36" :step="1" v-model="spokeCount" />
|
||||||
|
<PreviewSlider title="Ring Thickness" :min="0.01" :max="0.3" :step="0.01" v-model="ringThickness" />
|
||||||
|
<PreviewSlider title="Spoke Thickness" :min="0.01" :max="0.2" :step="0.01" v-model="spokeThickness" />
|
||||||
|
<PreviewSlider title="Sweep Speed" :min="0.1" :max="5" :step="0.1" v-model="sweepSpeed" />
|
||||||
|
<PreviewSlider title="Sweep Width" :min="1" :max="20" :step="1" v-model="sweepWidth" />
|
||||||
|
<PreviewSlider title="Sweep Lobes" :min="1" :max="6" :step="1" v-model="sweepLobes" />
|
||||||
|
<PreviewSlider title="Falloff" :min="0.1" :max="3" :step="0.1" v-model="falloff" />
|
||||||
|
<PreviewSlider title="Brightness" :min="0.1" :max="3" :step="0.1" v-model="brightness" />
|
||||||
|
<PreviewSwitch title="Mouse Interaction" v-model="enableMouseInteraction" />
|
||||||
|
<PreviewSlider
|
||||||
|
v-if="enableMouseInteraction"
|
||||||
|
title="Mouse Influence"
|
||||||
|
:min="0.1"
|
||||||
|
:max="1"
|
||||||
|
:step="0.05"
|
||||||
|
v-model="mouseInfluence"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="radar" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="radar.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
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 PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
|
import { radar } from '@/constants/code/Backgrounds/radarCode';
|
||||||
|
import Radar from '@/content/Backgrounds/Radar/Radar.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const speed = ref(1.0);
|
||||||
|
const scale = ref(0.5);
|
||||||
|
const ringCount = ref(10.0);
|
||||||
|
const spokeCount = ref(10.0);
|
||||||
|
const ringThickness = ref(0.05);
|
||||||
|
const spokeThickness = ref(0.01);
|
||||||
|
const sweepSpeed = ref(1.0);
|
||||||
|
const sweepWidth = ref(2.0);
|
||||||
|
const sweepLobes = ref(1.0);
|
||||||
|
const color = ref('#9f29ff');
|
||||||
|
const backgroundColor = ref('#000000');
|
||||||
|
const falloff = ref(2.0);
|
||||||
|
const brightness = ref(1.0);
|
||||||
|
const enableMouseInteraction = ref(true);
|
||||||
|
const mouseInfluence = ref(0.1);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'speed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Overall animation speed multiplier.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scale',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.5',
|
||||||
|
description: 'Zoom level of the radar pattern.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ringCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '10.0',
|
||||||
|
description: 'Number of concentric rings.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spokeCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '10.0',
|
||||||
|
description: 'Number of radial spoke lines.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ringThickness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.05',
|
||||||
|
description: 'Thickness of the concentric ring lines.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'spokeThickness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.01',
|
||||||
|
description: 'Thickness of the radial spoke lines.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sweepSpeed',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Rotation speed of the sweep beam.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sweepWidth',
|
||||||
|
type: 'number',
|
||||||
|
default: '2.0',
|
||||||
|
description: 'Width of the sweep trail (higher = thinner).'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sweepLobes',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Number of sweep beams around the radar.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#9f29ff"',
|
||||||
|
description: 'Primary radar color in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'backgroundColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '"#000000"',
|
||||||
|
description: 'Background color in HEX format.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'falloff',
|
||||||
|
type: 'number',
|
||||||
|
default: '2.0',
|
||||||
|
description: 'Edge fade intensity based on distance from center.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'brightness',
|
||||||
|
type: 'number',
|
||||||
|
default: '1.0',
|
||||||
|
description: 'Overall brightness multiplier.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableMouseInteraction',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'true',
|
||||||
|
description: 'Enable cursor-reactive center offset.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mouseInfluence',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.1',
|
||||||
|
description: 'Strength of the mouse offset effect.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user