mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-09 00:19:31 -06:00
Component Boom
This commit is contained in:
266
src/content/Backgrounds/Aurora/Aurora.vue
Normal file
266
src/content/Backgrounds/Aurora/Aurora.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div ref="containerRef" :class="className" :style="style" class="w-full h-full"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, type CSSProperties } from 'vue'
|
||||
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl'
|
||||
|
||||
interface AuroraProps {
|
||||
colorStops?: string[]
|
||||
amplitude?: number
|
||||
blend?: number
|
||||
time?: number
|
||||
speed?: number
|
||||
intensity?: number
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<AuroraProps>(), {
|
||||
colorStops: () => ['#7cff67', '#171D22', '#7cff67'],
|
||||
amplitude: 1.0,
|
||||
blend: 0.5,
|
||||
speed: 1.0,
|
||||
intensity: 1.0,
|
||||
className: '',
|
||||
style: () => ({})
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
|
||||
const VERT = `#version 300 es
|
||||
in vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const FRAG = `#version 300 es
|
||||
precision highp float;
|
||||
|
||||
uniform float uTime;
|
||||
uniform float uAmplitude;
|
||||
uniform vec3 uColorStops[3];
|
||||
uniform vec2 uResolution;
|
||||
uniform float uBlend;
|
||||
uniform float uIntensity;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
vec3 permute(vec3 x) {
|
||||
return mod(((x * 34.0) + 1.0) * x, 289.0);
|
||||
}
|
||||
|
||||
float snoise(vec2 v){
|
||||
const vec4 C = vec4(
|
||||
0.211324865405187, 0.366025403784439,
|
||||
-0.577350269189626, 0.024390243902439
|
||||
);
|
||||
vec2 i = floor(v + dot(v, C.yy));
|
||||
vec2 x0 = v - i + dot(i, C.xx);
|
||||
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||
vec4 x12 = x0.xyxy + C.xxzz;
|
||||
x12.xy -= i1;
|
||||
i = mod(i, 289.0);
|
||||
|
||||
vec3 p = permute(
|
||||
permute(i.y + vec3(0.0, i1.y, 1.0))
|
||||
+ i.x + vec3(0.0, i1.x, 1.0)
|
||||
);
|
||||
|
||||
vec3 m = max(
|
||||
0.5 - vec3(
|
||||
dot(x0, x0),
|
||||
dot(x12.xy, x12.xy),
|
||||
dot(x12.zw, x12.zw)
|
||||
),
|
||||
0.0
|
||||
);
|
||||
m = m * m;
|
||||
m = m * m;
|
||||
|
||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||
vec3 h = abs(x) - 0.5;
|
||||
vec3 ox = floor(x + 0.5);
|
||||
vec3 a0 = x - ox;
|
||||
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
|
||||
|
||||
vec3 g;
|
||||
g.x = a0.x * x0.x + h.x * x0.y;
|
||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||
return 130.0 * dot(m, g);
|
||||
}
|
||||
|
||||
struct ColorStop {
|
||||
vec3 color;
|
||||
float position;
|
||||
};
|
||||
|
||||
#define COLOR_RAMP(colors, factor, finalColor) { \
|
||||
int index = 0; \
|
||||
for (int i = 0; i < 2; i++) { \
|
||||
ColorStop currentColor = colors[i]; \
|
||||
bool isInBetween = currentColor.position <= factor; \
|
||||
index = int(mix(float(index), float(i), float(isInBetween))); \
|
||||
} \
|
||||
ColorStop currentColor = colors[index]; \
|
||||
ColorStop nextColor = colors[index + 1]; \
|
||||
float range = nextColor.position - currentColor.position; \
|
||||
float lerpFactor = (factor - currentColor.position) / range; \
|
||||
finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / uResolution;
|
||||
|
||||
ColorStop colors[3];
|
||||
colors[0] = ColorStop(uColorStops[0], 0.0);
|
||||
colors[1] = ColorStop(uColorStops[1], 0.5);
|
||||
colors[2] = ColorStop(uColorStops[2], 1.0);
|
||||
|
||||
vec3 rampColor;
|
||||
COLOR_RAMP(colors, uv.x, rampColor);
|
||||
|
||||
float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;
|
||||
height = exp(height);
|
||||
height = (uv.y * 2.0 - height + 0.2);
|
||||
float intensity = 0.6 * height;
|
||||
|
||||
float midPoint = 0.20;
|
||||
float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);
|
||||
|
||||
vec3 auroraColor = rampColor;
|
||||
|
||||
float finalAlpha = auroraAlpha * smoothstep(0.0, 0.5, intensity) * uIntensity;
|
||||
|
||||
fragColor = vec4(auroraColor * finalAlpha, finalAlpha);
|
||||
}
|
||||
`
|
||||
|
||||
let renderer: Renderer | null = null
|
||||
let animateId = 0
|
||||
|
||||
const initAurora = () => {
|
||||
const container = containerRef.value
|
||||
if (!container) return
|
||||
|
||||
renderer = new Renderer({
|
||||
alpha: true,
|
||||
premultipliedAlpha: true,
|
||||
antialias: true,
|
||||
})
|
||||
|
||||
const gl = renderer.gl
|
||||
gl.clearColor(0, 0, 0, 0)
|
||||
gl.enable(gl.BLEND)
|
||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
gl.canvas.style.backgroundColor = 'transparent'
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let program: Program | undefined
|
||||
|
||||
const resize = () => {
|
||||
if (!container) return
|
||||
const width = container.offsetWidth
|
||||
const height = container.offsetHeight
|
||||
renderer!.setSize(width, height)
|
||||
if (program) {
|
||||
program.uniforms.uResolution.value = [width, height]
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
|
||||
const geometry = new Triangle(gl)
|
||||
if (geometry.attributes.uv) {
|
||||
delete geometry.attributes.uv
|
||||
}
|
||||
|
||||
const colorStopsArray = props.colorStops.map((hex) => {
|
||||
const c = new Color(hex)
|
||||
return [c.r, c.g, c.b]
|
||||
})
|
||||
|
||||
program = new Program(gl, {
|
||||
vertex: VERT,
|
||||
fragment: FRAG,
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uAmplitude: { value: props.amplitude },
|
||||
uColorStops: { value: colorStopsArray },
|
||||
uResolution: { value: [container.offsetWidth, container.offsetHeight] },
|
||||
uBlend: { value: props.blend },
|
||||
uIntensity: { value: props.intensity },
|
||||
},
|
||||
})
|
||||
|
||||
const mesh = new Mesh(gl, { geometry, program })
|
||||
container.appendChild(gl.canvas)
|
||||
|
||||
gl.canvas.style.width = '100%'
|
||||
gl.canvas.style.height = '100%'
|
||||
gl.canvas.style.display = 'block'
|
||||
|
||||
const update = (t: number) => {
|
||||
animateId = requestAnimationFrame(update)
|
||||
const time = props.time ?? t * 0.01
|
||||
const speed = props.speed ?? 1.0
|
||||
if (program) {
|
||||
program.uniforms.uTime.value = time * speed * 0.1
|
||||
program.uniforms.uAmplitude.value = props.amplitude ?? 1.0
|
||||
program.uniforms.uBlend.value = props.blend ?? 0.5
|
||||
program.uniforms.uIntensity.value = props.intensity ?? 1.0
|
||||
const stops = props.colorStops ?? ['#27FF64', '#7cff67', '#27FF64']
|
||||
program.uniforms.uColorStops.value = stops.map((hex: string) => {
|
||||
const c = new Color(hex)
|
||||
return [c.r, c.g, c.b]
|
||||
})
|
||||
renderer!.render({ scene: mesh })
|
||||
}
|
||||
}
|
||||
animateId = requestAnimationFrame(update)
|
||||
|
||||
resize()
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animateId)
|
||||
window.removeEventListener('resize', resize)
|
||||
if (container && gl.canvas.parentNode === container) {
|
||||
container.removeChild(gl.canvas)
|
||||
}
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
if (animateId) {
|
||||
cancelAnimationFrame(animateId)
|
||||
}
|
||||
if (renderer) {
|
||||
const gl = renderer.gl
|
||||
const container = containerRef.value
|
||||
if (container && gl.canvas.parentNode === container) {
|
||||
container.removeChild(gl.canvas)
|
||||
}
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
renderer = null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initAurora()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [props.amplitude, props.intensity],
|
||||
() => {
|
||||
cleanup()
|
||||
initAurora()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
204
src/content/Backgrounds/Iridescence/Iridescence.vue
Normal file
204
src/content/Backgrounds/Iridescence/Iridescence.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div ref="containerRef" class="w-full h-full" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl'
|
||||
import type { OGLRenderingContext } from 'ogl'
|
||||
|
||||
interface Props {
|
||||
color?: [number, number, number]
|
||||
speed?: number
|
||||
amplitude?: number
|
||||
mouseReact?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [1, 1, 1] as [number, number, number],
|
||||
speed: 1.0,
|
||||
amplitude: 0.1,
|
||||
mouseReact: true
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement | null>(null)
|
||||
const mousePos = ref({ x: 0.5, y: 0.5 })
|
||||
|
||||
let renderer: Renderer | null = null
|
||||
let gl: OGLRenderingContext | null = null
|
||||
let program: Program | null = null
|
||||
let mesh: Mesh | null = null
|
||||
let animationId: number | null = null
|
||||
|
||||
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 uColor;
|
||||
uniform vec3 uResolution;
|
||||
uniform vec2 uMouse;
|
||||
uniform float uAmplitude;
|
||||
uniform float uSpeed;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
float mr = min(uResolution.x, uResolution.y);
|
||||
vec2 uv = (vUv.xy * 2.0 - 1.0) * uResolution.xy / mr;
|
||||
|
||||
uv += (uMouse - vec2(0.5)) * uAmplitude;
|
||||
|
||||
float d = -uTime * 0.5 * uSpeed;
|
||||
float a = 0.0;
|
||||
for (float i = 0.0; i < 8.0; ++i) {
|
||||
a += cos(i - d - a * uv.x);
|
||||
d += sin(uv.y * i + a);
|
||||
}
|
||||
d += uTime * 0.5 * uSpeed;
|
||||
vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5);
|
||||
col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5) * uColor;
|
||||
gl_FragColor = vec4(col, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const resize = () => {
|
||||
if (!containerRef.value || !renderer || !program || !gl) return
|
||||
|
||||
const container = containerRef.value
|
||||
const scale = 1
|
||||
renderer.setSize(container.offsetWidth * scale, container.offsetHeight * scale)
|
||||
|
||||
if (program) {
|
||||
program.uniforms.uResolution.value = new Color(
|
||||
gl.canvas.width,
|
||||
gl.canvas.height,
|
||||
gl.canvas.width / gl.canvas.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!containerRef.value || !program) return
|
||||
|
||||
const rect = containerRef.value.getBoundingClientRect()
|
||||
const x = (e.clientX - rect.left) / rect.width
|
||||
const y = 1.0 - (e.clientY - rect.top) / rect.height
|
||||
|
||||
mousePos.value = { x, y }
|
||||
if (program.uniforms.uMouse.value) {
|
||||
program.uniforms.uMouse.value[0] = x
|
||||
program.uniforms.uMouse.value[1] = y
|
||||
}
|
||||
}
|
||||
|
||||
const update = (t: number) => {
|
||||
if (!program || !renderer || !mesh) return
|
||||
|
||||
animationId = requestAnimationFrame(update)
|
||||
program.uniforms.uTime.value = t * 0.001
|
||||
renderer.render({ scene: mesh })
|
||||
}
|
||||
|
||||
const initializeScene = () => {
|
||||
if (!containerRef.value) return
|
||||
|
||||
cleanup()
|
||||
|
||||
const container = containerRef.value
|
||||
renderer = new Renderer()
|
||||
gl = renderer.gl
|
||||
gl.clearColor(1, 1, 1, 1)
|
||||
|
||||
const geometry = new Triangle(gl)
|
||||
program = new Program(gl, {
|
||||
vertex: vertexShader,
|
||||
fragment: fragmentShader,
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uColor: { value: new Color(...props.color) },
|
||||
uResolution: {
|
||||
value: new Color(
|
||||
gl.canvas.width,
|
||||
gl.canvas.height,
|
||||
gl.canvas.width / gl.canvas.height
|
||||
)
|
||||
},
|
||||
uMouse: { value: new Float32Array([mousePos.value.x, mousePos.value.y]) },
|
||||
uAmplitude: { value: props.amplitude },
|
||||
uSpeed: { value: props.speed }
|
||||
}
|
||||
})
|
||||
|
||||
mesh = new Mesh(gl, { geometry, program })
|
||||
|
||||
const canvas = gl.canvas as HTMLCanvasElement
|
||||
canvas.style.width = '100%'
|
||||
canvas.style.height = '100%'
|
||||
canvas.style.display = 'block'
|
||||
|
||||
container.appendChild(canvas)
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
if (props.mouseReact) {
|
||||
container.addEventListener('mousemove', handleMouseMove)
|
||||
}
|
||||
|
||||
resize()
|
||||
animationId = requestAnimationFrame(update)
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
animationId = null
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', resize)
|
||||
|
||||
if (containerRef.value) {
|
||||
containerRef.value.removeEventListener('mousemove', handleMouseMove)
|
||||
|
||||
const canvas = containerRef.value.querySelector('canvas')
|
||||
if (canvas) {
|
||||
containerRef.value.removeChild(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
if (gl) {
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
|
||||
renderer = null
|
||||
gl = null
|
||||
program = null
|
||||
mesh = null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initializeScene()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
watch(
|
||||
[() => props.color, () => props.speed, () => props.amplitude, () => props.mouseReact],
|
||||
() => {
|
||||
initializeScene()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
236
src/content/Backgrounds/Lightning/Lightning.vue
Normal file
236
src/content/Backgrounds/Lightning/Lightning.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<canvas ref="canvasRef" class="w-full h-full block mix-blend-screen"></canvas>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
interface LightningProps {
|
||||
hue?: number
|
||||
xOffset?: number
|
||||
speed?: number
|
||||
intensity?: number
|
||||
size?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LightningProps>(), {
|
||||
hue: 230,
|
||||
xOffset: 0,
|
||||
speed: 1,
|
||||
intensity: 1,
|
||||
size: 1
|
||||
})
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
let animationId = 0
|
||||
let gl: WebGLRenderingContext | null = null
|
||||
let program: WebGLProgram | null = null
|
||||
let startTime = 0
|
||||
|
||||
const vertexShaderSource = `
|
||||
attribute vec2 aPosition;
|
||||
void main() {
|
||||
gl_Position = vec4(aPosition, 0.0, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const fragmentShaderSource = `
|
||||
precision mediump float;
|
||||
uniform vec2 iResolution;
|
||||
uniform float iTime;
|
||||
uniform float uHue;
|
||||
uniform float uXOffset;
|
||||
uniform float uSpeed;
|
||||
uniform float uIntensity;
|
||||
uniform float uSize;
|
||||
|
||||
#define OCTAVE_COUNT 10
|
||||
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0,4.0,2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
|
||||
return c.z * mix(vec3(1.0), rgb, c.y);
|
||||
}
|
||||
|
||||
float hash11(float p) {
|
||||
p = fract(p * .1031);
|
||||
p *= p + 33.33;
|
||||
p *= p + p;
|
||||
return fract(p);
|
||||
}
|
||||
|
||||
float hash12(vec2 p) {
|
||||
vec3 p3 = fract(vec3(p.xyx) * .1031);
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
mat2 rotate2d(float theta) {
|
||||
float c = cos(theta);
|
||||
float s = sin(theta);
|
||||
return mat2(c, -s, s, c);
|
||||
}
|
||||
|
||||
float noise(vec2 p) {
|
||||
vec2 ip = floor(p);
|
||||
vec2 fp = fract(p);
|
||||
float a = hash12(ip);
|
||||
float b = hash12(ip + vec2(1.0, 0.0));
|
||||
float c = hash12(ip + vec2(0.0, 1.0));
|
||||
float d = hash12(ip + vec2(1.0, 1.0));
|
||||
|
||||
vec2 t = smoothstep(0.0, 1.0, fp);
|
||||
return mix(mix(a, b, t.x), mix(c, d, t.x), t.y);
|
||||
}
|
||||
|
||||
float fbm(vec2 p) {
|
||||
float value = 0.0;
|
||||
float amplitude = 0.5;
|
||||
for (int i = 0; i < OCTAVE_COUNT; ++i) {
|
||||
value += amplitude * noise(p);
|
||||
p *= rotate2d(0.45);
|
||||
p *= 2.0;
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
uv = 2.0 * uv - 1.0;
|
||||
uv.x *= iResolution.x / iResolution.y;
|
||||
uv.x += uXOffset;
|
||||
|
||||
uv += 2.0 * fbm(uv * uSize + 0.8 * iTime * uSpeed) - 1.0;
|
||||
|
||||
float dist = abs(uv.x);
|
||||
vec3 baseColor = hsv2rgb(vec3(uHue / 360.0, 0.7, 0.8));
|
||||
vec3 col = baseColor * pow(mix(0.0, 0.07, hash11(iTime * uSpeed)) / dist, 1.0) * uIntensity;
|
||||
col = pow(col, vec3(1.0));
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
mainImage(gl_FragColor, gl_FragCoord.xy);
|
||||
}
|
||||
`
|
||||
|
||||
const compileShader = (source: string, type: number): WebGLShader | null => {
|
||||
if (!gl) return null
|
||||
const shader = gl.createShader(type)
|
||||
if (!shader) return null
|
||||
gl.shaderSource(shader, source)
|
||||
gl.compileShader(shader)
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
console.error('Shader compile error:', gl.getShaderInfoLog(shader))
|
||||
gl.deleteShader(shader)
|
||||
return null
|
||||
}
|
||||
return shader
|
||||
}
|
||||
|
||||
const initWebGL = () => {
|
||||
const canvas = canvasRef.value
|
||||
if (!canvas) return
|
||||
|
||||
const resizeCanvas = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
canvas.width = rect.width
|
||||
canvas.height = rect.height
|
||||
canvas.style.width = rect.width + 'px'
|
||||
canvas.style.height = rect.height + 'px'
|
||||
}
|
||||
|
||||
resizeCanvas()
|
||||
window.addEventListener('resize', resizeCanvas)
|
||||
|
||||
gl = canvas.getContext('webgl')
|
||||
if (!gl) {
|
||||
console.error('WebGL not supported')
|
||||
return
|
||||
}
|
||||
|
||||
const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER)
|
||||
const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER)
|
||||
if (!vertexShader || !fragmentShader) return
|
||||
|
||||
program = gl.createProgram()
|
||||
if (!program) return
|
||||
gl.attachShader(program, vertexShader)
|
||||
gl.attachShader(program, fragmentShader)
|
||||
gl.linkProgram(program)
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
console.error('Program linking error:', gl.getProgramInfoLog(program))
|
||||
return
|
||||
}
|
||||
gl.useProgram(program)
|
||||
|
||||
const vertices = new Float32Array([
|
||||
-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1,
|
||||
])
|
||||
const vertexBuffer = gl.createBuffer()
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
|
||||
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
|
||||
|
||||
const aPosition = gl.getAttribLocation(program, 'aPosition')
|
||||
gl.enableVertexAttribArray(aPosition)
|
||||
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
startTime = performance.now()
|
||||
render()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resizeCanvas)
|
||||
}
|
||||
}
|
||||
|
||||
const render = () => {
|
||||
if (!gl || !program || !canvasRef.value) return
|
||||
|
||||
const canvas = canvasRef.value
|
||||
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
if (canvas.width !== rect.width || canvas.height !== rect.height) {
|
||||
canvas.width = rect.width
|
||||
canvas.height = rect.height
|
||||
canvas.style.width = rect.width + 'px'
|
||||
canvas.style.height = rect.height + 'px'
|
||||
}
|
||||
|
||||
gl.viewport(0, 0, canvas.width, canvas.height)
|
||||
|
||||
const iResolutionLocation = gl.getUniformLocation(program, 'iResolution')
|
||||
const iTimeLocation = gl.getUniformLocation(program, 'iTime')
|
||||
const uHueLocation = gl.getUniformLocation(program, 'uHue')
|
||||
const uXOffsetLocation = gl.getUniformLocation(program, 'uXOffset')
|
||||
const uSpeedLocation = gl.getUniformLocation(program, 'uSpeed')
|
||||
const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity')
|
||||
const uSizeLocation = gl.getUniformLocation(program, 'uSize')
|
||||
|
||||
gl.uniform2f(iResolutionLocation, canvas.width, canvas.height)
|
||||
const currentTime = performance.now()
|
||||
gl.uniform1f(iTimeLocation, (currentTime - startTime) / 1000.0)
|
||||
gl.uniform1f(uHueLocation, props.hue)
|
||||
gl.uniform1f(uXOffsetLocation, props.xOffset)
|
||||
gl.uniform1f(uSpeedLocation, props.speed)
|
||||
gl.uniform1f(uIntensityLocation, props.intensity)
|
||||
gl.uniform1f(uSizeLocation, props.size)
|
||||
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6)
|
||||
animationId = requestAnimationFrame(render)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initWebGL()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [props.hue, props.xOffset, props.speed, props.intensity, props.size],
|
||||
() => {}
|
||||
)
|
||||
</script>
|
||||
314
src/content/Backgrounds/Particles/Particles.vue
Normal file
314
src/content/Backgrounds/Particles/Particles.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div ref="containerRef" :class="className" class="relative w-full h-full"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { Renderer, Camera, Geometry, Program, Mesh } from 'ogl'
|
||||
|
||||
interface ParticlesProps {
|
||||
particleCount?: number
|
||||
particleSpread?: number
|
||||
speed?: number
|
||||
particleColors?: string[]
|
||||
moveParticlesOnHover?: boolean
|
||||
particleHoverFactor?: number
|
||||
alphaParticles?: boolean
|
||||
particleBaseSize?: number
|
||||
sizeRandomness?: number
|
||||
cameraDistance?: number
|
||||
disableRotation?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ParticlesProps>(), {
|
||||
particleCount: 200,
|
||||
particleSpread: 10,
|
||||
speed: 0.1,
|
||||
particleColors: () => ['#ffffff'],
|
||||
moveParticlesOnHover: false,
|
||||
particleHoverFactor: 1,
|
||||
alphaParticles: false,
|
||||
particleBaseSize: 100,
|
||||
sizeRandomness: 1,
|
||||
cameraDistance: 20,
|
||||
disableRotation: false,
|
||||
className: ''
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
const mouseRef = ref({ x: 0, y: 0 })
|
||||
|
||||
let renderer: Renderer | null = null
|
||||
let camera: Camera | null = null
|
||||
let particles: Mesh | null = null
|
||||
let program: Program | null = null
|
||||
let animationFrameId: number | null = null
|
||||
let lastTime = 0
|
||||
let elapsed = 0
|
||||
|
||||
const defaultColors = ['#ffffff', '#ffffff', '#ffffff']
|
||||
|
||||
const hexToRgb = (hex: string): [number, number, number] => {
|
||||
hex = hex.replace(/^#/, '')
|
||||
if (hex.length === 3) {
|
||||
hex = hex.split('').map((c) => c + c).join('')
|
||||
}
|
||||
const int = parseInt(hex, 16)
|
||||
const r = ((int >> 16) & 255) / 255
|
||||
const g = ((int >> 8) & 255) / 255
|
||||
const b = (int & 255) / 255
|
||||
return [r, g, b]
|
||||
}
|
||||
|
||||
const vertex = /* glsl */ `
|
||||
attribute vec3 position;
|
||||
attribute vec4 random;
|
||||
attribute vec3 color;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform float uTime;
|
||||
uniform float uSpread;
|
||||
uniform float uBaseSize;
|
||||
uniform float uSizeRandomness;
|
||||
|
||||
varying vec4 vRandom;
|
||||
varying vec3 vColor;
|
||||
|
||||
void main() {
|
||||
vRandom = random;
|
||||
vColor = color;
|
||||
|
||||
vec3 pos = position * uSpread;
|
||||
pos.z *= 10.0;
|
||||
|
||||
vec4 mPos = modelMatrix * vec4(pos, 1.0);
|
||||
float t = uTime;
|
||||
mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);
|
||||
mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);
|
||||
mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);
|
||||
|
||||
vec4 mvPos = viewMatrix * mPos;
|
||||
gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);
|
||||
gl_Position = projectionMatrix * mvPos;
|
||||
}
|
||||
`
|
||||
|
||||
const fragment = /* glsl */ `
|
||||
precision highp float;
|
||||
|
||||
uniform float uTime;
|
||||
uniform float uAlphaParticles;
|
||||
varying vec4 vRandom;
|
||||
varying vec3 vColor;
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_PointCoord.xy;
|
||||
float d = length(uv - vec2(0.5));
|
||||
|
||||
if(uAlphaParticles < 0.5) {
|
||||
if(d > 0.5) {
|
||||
discard;
|
||||
}
|
||||
gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);
|
||||
} else {
|
||||
float circle = smoothstep(0.5, 0.4, d) * 0.8;
|
||||
gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const container = containerRef.value
|
||||
if (!container) return
|
||||
|
||||
const rect = container.getBoundingClientRect()
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1
|
||||
const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1)
|
||||
mouseRef.value = { x, y }
|
||||
}
|
||||
|
||||
const initParticles = () => {
|
||||
const container = containerRef.value
|
||||
if (!container) return
|
||||
|
||||
renderer = new Renderer({ depth: false, alpha: true })
|
||||
const gl = renderer.gl
|
||||
container.appendChild(gl.canvas)
|
||||
gl.clearColor(0, 0, 0, 0)
|
||||
|
||||
gl.canvas.style.width = '100%'
|
||||
gl.canvas.style.height = '100%'
|
||||
gl.canvas.style.display = 'block'
|
||||
|
||||
camera = new Camera(gl, { fov: 15 })
|
||||
camera.position.set(0, 0, props.cameraDistance)
|
||||
|
||||
const resize = () => {
|
||||
const width = container.clientWidth
|
||||
const height = container.clientHeight
|
||||
renderer!.setSize(width, height)
|
||||
camera!.perspective({ aspect: width / height })
|
||||
|
||||
gl.canvas.style.width = '100%'
|
||||
gl.canvas.style.height = '100%'
|
||||
gl.canvas.style.display = 'block'
|
||||
}
|
||||
window.addEventListener('resize', resize, false)
|
||||
resize()
|
||||
|
||||
if (props.moveParticlesOnHover) {
|
||||
container.addEventListener('mousemove', handleMouseMove)
|
||||
}
|
||||
|
||||
const count = props.particleCount
|
||||
const positions = new Float32Array(count * 3)
|
||||
const randoms = new Float32Array(count * 4)
|
||||
const colors = new Float32Array(count * 3)
|
||||
const palette = props.particleColors && props.particleColors.length > 0 ? props.particleColors : defaultColors
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
let x: number, y: number, z: number, len: number
|
||||
do {
|
||||
x = Math.random() * 2 - 1
|
||||
y = Math.random() * 2 - 1
|
||||
z = Math.random() * 2 - 1
|
||||
len = x * x + y * y + z * z
|
||||
} while (len > 1 || len === 0)
|
||||
const r = Math.cbrt(Math.random())
|
||||
positions.set([x * r, y * r, z * r], i * 3)
|
||||
randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4)
|
||||
const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)])
|
||||
colors.set(col, i * 3)
|
||||
}
|
||||
|
||||
const geometry = new Geometry(gl, {
|
||||
position: { size: 3, data: positions },
|
||||
random: { size: 4, data: randoms },
|
||||
color: { size: 3, data: colors },
|
||||
})
|
||||
|
||||
program = new Program(gl, {
|
||||
vertex,
|
||||
fragment,
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uSpread: { value: props.particleSpread },
|
||||
uBaseSize: { value: props.particleBaseSize },
|
||||
uSizeRandomness: { value: props.sizeRandomness },
|
||||
uAlphaParticles: { value: props.alphaParticles ? 1 : 0 },
|
||||
},
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
})
|
||||
|
||||
particles = new Mesh(gl, { mode: gl.POINTS, geometry, program })
|
||||
|
||||
lastTime = performance.now()
|
||||
elapsed = 0
|
||||
|
||||
const update = (t: number) => {
|
||||
if (!animationFrameId) return
|
||||
animationFrameId = requestAnimationFrame(update)
|
||||
const delta = t - lastTime
|
||||
lastTime = t
|
||||
elapsed += delta * props.speed
|
||||
|
||||
if (program) {
|
||||
program.uniforms.uTime.value = elapsed * 0.001
|
||||
program.uniforms.uSpread.value = props.particleSpread
|
||||
program.uniforms.uBaseSize.value = props.particleBaseSize
|
||||
program.uniforms.uSizeRandomness.value = props.sizeRandomness
|
||||
program.uniforms.uAlphaParticles.value = props.alphaParticles ? 1 : 0
|
||||
}
|
||||
|
||||
if (particles) {
|
||||
if (props.moveParticlesOnHover) {
|
||||
particles.position.x = -mouseRef.value.x * props.particleHoverFactor
|
||||
particles.position.y = -mouseRef.value.y * props.particleHoverFactor
|
||||
} else {
|
||||
particles.position.x = 0
|
||||
particles.position.y = 0
|
||||
}
|
||||
|
||||
if (!props.disableRotation) {
|
||||
particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1
|
||||
particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15
|
||||
particles.rotation.z += 0.01 * props.speed
|
||||
}
|
||||
}
|
||||
|
||||
if (renderer && camera && particles) {
|
||||
renderer.render({ scene: particles, camera })
|
||||
}
|
||||
}
|
||||
|
||||
animationFrameId = requestAnimationFrame(update)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize)
|
||||
if (props.moveParticlesOnHover) {
|
||||
container.removeEventListener('mousemove', handleMouseMove)
|
||||
}
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId)
|
||||
animationFrameId = null
|
||||
}
|
||||
if (container.contains(gl.canvas)) {
|
||||
container.removeChild(gl.canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId)
|
||||
animationFrameId = null
|
||||
}
|
||||
if (renderer) {
|
||||
const container = containerRef.value
|
||||
const gl = renderer.gl
|
||||
if (container && gl.canvas.parentNode === container) {
|
||||
container.removeChild(gl.canvas)
|
||||
}
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
renderer = null
|
||||
camera = null
|
||||
particles = null
|
||||
program = null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initParticles()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [props.particleCount, props.particleColors],
|
||||
() => {
|
||||
cleanup()
|
||||
initParticles()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [
|
||||
props.particleSpread,
|
||||
props.speed,
|
||||
props.particleBaseSize,
|
||||
props.sizeRandomness,
|
||||
props.alphaParticles,
|
||||
props.moveParticlesOnHover,
|
||||
props.particleHoverFactor,
|
||||
props.disableRotation
|
||||
],
|
||||
() => {}
|
||||
)
|
||||
</script>
|
||||
232
src/content/Backgrounds/Silk/Silk.vue
Normal file
232
src/content/Backgrounds/Silk/Silk.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div ref="containerRef" :class="className" :style="style" class="w-full h-full"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, type CSSProperties } from 'vue'
|
||||
import { Renderer, Program, Mesh, Plane, Camera } from 'ogl'
|
||||
|
||||
interface SilkProps {
|
||||
speed?: number
|
||||
scale?: number
|
||||
color?: string
|
||||
noiseIntensity?: number
|
||||
rotation?: number
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<SilkProps>(), {
|
||||
speed: 5,
|
||||
scale: 1,
|
||||
color: '#7B7481',
|
||||
noiseIntensity: 1.5,
|
||||
rotation: 0,
|
||||
className: '',
|
||||
style: () => ({})
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
|
||||
const hexToNormalizedRGB = (hex: string): [number, number, number] => {
|
||||
const clean = hex.replace('#', '')
|
||||
const r = parseInt(clean.slice(0, 2), 16) / 255
|
||||
const g = parseInt(clean.slice(2, 4), 16) / 255
|
||||
const b = parseInt(clean.slice(4, 6), 16) / 255
|
||||
return [r, g, b]
|
||||
}
|
||||
|
||||
const vertexShader = `
|
||||
attribute vec2 uv;
|
||||
attribute vec3 position;
|
||||
|
||||
uniform mat4 modelViewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPosition;
|
||||
|
||||
void main() {
|
||||
vPosition = position;
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const fragmentShader = `
|
||||
precision highp float;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPosition;
|
||||
|
||||
uniform float uTime;
|
||||
uniform vec3 uColor;
|
||||
uniform float uSpeed;
|
||||
uniform float uScale;
|
||||
uniform float uRotation;
|
||||
uniform float uNoiseIntensity;
|
||||
|
||||
const float e = 2.71828182845904523536;
|
||||
|
||||
float noise(vec2 texCoord) {
|
||||
float G = e;
|
||||
vec2 r = (G * sin(G * texCoord));
|
||||
return fract(r.x * r.y * (1.0 + texCoord.x));
|
||||
}
|
||||
|
||||
vec2 rotateUvs(vec2 uv, float angle) {
|
||||
float c = cos(angle);
|
||||
float s = sin(angle);
|
||||
mat2 rot = mat2(c, -s, s, c);
|
||||
return rot * uv;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float rnd = noise(gl_FragCoord.xy);
|
||||
vec2 uv = rotateUvs(vUv * uScale, uRotation);
|
||||
vec2 tex = uv * uScale;
|
||||
float tOffset = uSpeed * uTime;
|
||||
|
||||
tex.y += 0.03 * sin(8.0 * tex.x - tOffset);
|
||||
|
||||
float pattern = 0.6 +
|
||||
0.4 * sin(5.0 * (tex.x + tex.y +
|
||||
cos(3.0 * tex.x + 5.0 * tex.y) +
|
||||
0.02 * tOffset) +
|
||||
sin(20.0 * (tex.x + tex.y - 0.1 * tOffset)));
|
||||
|
||||
vec4 col = vec4(uColor, 1.0) * vec4(pattern) - rnd / 15.0 * uNoiseIntensity;
|
||||
col.a = 1.0;
|
||||
gl_FragColor = col;
|
||||
}
|
||||
`
|
||||
|
||||
let renderer: Renderer | null = null
|
||||
let mesh: Mesh | null = null
|
||||
let program: Program | null = null
|
||||
let camera: Camera | null = null
|
||||
let animateId = 0
|
||||
|
||||
const initSilk = () => {
|
||||
const container = containerRef.value
|
||||
if (!container) return
|
||||
|
||||
renderer = new Renderer({
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
})
|
||||
|
||||
const gl = renderer.gl
|
||||
gl.clearColor(0, 0, 0, 0)
|
||||
gl.canvas.style.backgroundColor = 'transparent'
|
||||
|
||||
camera = new Camera(gl, { fov: 75 })
|
||||
camera.position.z = 1
|
||||
|
||||
const resize = () => {
|
||||
if (!container || !camera) return
|
||||
const width = container.offsetWidth
|
||||
const height = container.offsetHeight
|
||||
renderer!.setSize(width, height)
|
||||
camera.perspective({ aspect: width / height })
|
||||
|
||||
if (mesh) {
|
||||
const distance = camera.position.z
|
||||
const fov = camera.fov * (Math.PI / 180)
|
||||
const height2 = 2 * Math.tan(fov / 2) * distance
|
||||
const width2 = height2 * (width / height)
|
||||
|
||||
mesh.scale.set(width2, height2, 1)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
|
||||
const geometry = new Plane(gl, {
|
||||
width: 1,
|
||||
height: 1,
|
||||
})
|
||||
|
||||
const colorRGB = hexToNormalizedRGB(props.color)
|
||||
|
||||
program = new Program(gl, {
|
||||
vertex: vertexShader,
|
||||
fragment: fragmentShader,
|
||||
uniforms: {
|
||||
uSpeed: { value: props.speed },
|
||||
uScale: { value: props.scale },
|
||||
uNoiseIntensity: { value: props.noiseIntensity },
|
||||
uColor: { value: colorRGB },
|
||||
uRotation: { value: props.rotation },
|
||||
uTime: { value: 0 },
|
||||
},
|
||||
})
|
||||
|
||||
mesh = new Mesh(gl, { geometry, program })
|
||||
container.appendChild(gl.canvas)
|
||||
|
||||
gl.canvas.style.width = '100%'
|
||||
gl.canvas.style.height = '100%'
|
||||
gl.canvas.style.display = 'block'
|
||||
|
||||
let lastTime = 0
|
||||
const update = (t: number) => {
|
||||
animateId = requestAnimationFrame(update)
|
||||
const deltaTime = (t - lastTime) / 1000
|
||||
lastTime = t
|
||||
|
||||
if (program && mesh && camera) {
|
||||
program.uniforms.uTime.value += 0.1 * deltaTime
|
||||
program.uniforms.uSpeed.value = props.speed
|
||||
program.uniforms.uScale.value = props.scale
|
||||
program.uniforms.uNoiseIntensity.value = props.noiseIntensity
|
||||
program.uniforms.uColor.value = hexToNormalizedRGB(props.color)
|
||||
program.uniforms.uRotation.value = props.rotation
|
||||
renderer!.render({ scene: mesh, camera })
|
||||
}
|
||||
}
|
||||
animateId = requestAnimationFrame(update)
|
||||
|
||||
resize()
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animateId)
|
||||
window.removeEventListener('resize', resize)
|
||||
if (container && gl.canvas.parentNode === container) {
|
||||
container.removeChild(gl.canvas)
|
||||
}
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
if (animateId) {
|
||||
cancelAnimationFrame(animateId)
|
||||
}
|
||||
if (renderer) {
|
||||
const gl = renderer.gl
|
||||
const container = containerRef.value
|
||||
if (container && gl.canvas.parentNode === container) {
|
||||
container.removeChild(gl.canvas)
|
||||
}
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
renderer = null
|
||||
mesh = null
|
||||
camera = null
|
||||
program = null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initSilk()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [props.speed, props.scale, props.color, props.noiseIntensity, props.rotation],
|
||||
() => {}
|
||||
)
|
||||
</script>
|
||||
290
src/content/Backgrounds/Threads/Threads.vue
Normal file
290
src/content/Backgrounds/Threads/Threads.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<div ref="containerRef" class="w-full h-full relative" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { Renderer, Program, Mesh, Triangle, Color } from 'ogl'
|
||||
import type { OGLRenderingContext } from 'ogl'
|
||||
|
||||
interface Props {
|
||||
color?: [number, number, number]
|
||||
amplitude?: number
|
||||
distance?: number
|
||||
enableMouseInteraction?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [1, 1, 1] as [number, number, number],
|
||||
amplitude: 1,
|
||||
distance: 0,
|
||||
enableMouseInteraction: false
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
let renderer: Renderer | null = null
|
||||
let gl: OGLRenderingContext | null = null
|
||||
let program: Program | null = null
|
||||
let mesh: Mesh | null = null
|
||||
let animationId: number | null = null
|
||||
let currentMouse = [0.5, 0.5]
|
||||
let targetMouse = [0.5, 0.5]
|
||||
|
||||
const vertexShader = `
|
||||
attribute vec2 position;
|
||||
attribute vec2 uv;
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const fragmentShader = `
|
||||
precision highp float;
|
||||
|
||||
uniform float iTime;
|
||||
uniform vec3 iResolution;
|
||||
uniform vec3 uColor;
|
||||
uniform float uAmplitude;
|
||||
uniform float uDistance;
|
||||
uniform vec2 uMouse;
|
||||
|
||||
#define PI 3.1415926538
|
||||
|
||||
const int u_line_count = 40;
|
||||
const float u_line_width = 7.0;
|
||||
const float u_line_blur = 10.0;
|
||||
|
||||
float Perlin2D(vec2 P) {
|
||||
vec2 Pi = floor(P);
|
||||
vec4 Pf_Pfmin1 = P.xyxy - vec4(Pi, Pi + 1.0);
|
||||
vec4 Pt = vec4(Pi.xy, Pi.xy + 1.0);
|
||||
Pt = Pt - floor(Pt * (1.0 / 71.0)) * 71.0;
|
||||
Pt += vec2(26.0, 161.0).xyxy;
|
||||
Pt *= Pt;
|
||||
Pt = Pt.xzxz * Pt.yyww;
|
||||
vec4 hash_x = fract(Pt * (1.0 / 951.135664));
|
||||
vec4 hash_y = fract(Pt * (1.0 / 642.949883));
|
||||
vec4 grad_x = hash_x - 0.49999;
|
||||
vec4 grad_y = hash_y - 0.49999;
|
||||
vec4 grad_results = inversesqrt(grad_x * grad_x + grad_y * grad_y)
|
||||
* (grad_x * Pf_Pfmin1.xzxz + grad_y * Pf_Pfmin1.yyww);
|
||||
grad_results *= 1.4142135623730950;
|
||||
vec2 blend = Pf_Pfmin1.xy * Pf_Pfmin1.xy * Pf_Pfmin1.xy
|
||||
* (Pf_Pfmin1.xy * (Pf_Pfmin1.xy * 6.0 - 15.0) + 10.0);
|
||||
vec4 blend2 = vec4(blend, vec2(1.0 - blend));
|
||||
return dot(grad_results, blend2.zxzx * blend2.wwyy);
|
||||
}
|
||||
|
||||
float pixel(float count, vec2 resolution) {
|
||||
return (1.0 / max(resolution.x, resolution.y)) * count;
|
||||
}
|
||||
|
||||
float lineFn(vec2 st, float width, float perc, float offset, vec2 mouse, float time, float amplitude, float distance) {
|
||||
float split_offset = (perc * 0.4);
|
||||
float split_point = 0.1 + split_offset;
|
||||
|
||||
float amplitude_normal = smoothstep(split_point, 0.7, st.x);
|
||||
float amplitude_strength = 0.5;
|
||||
float finalAmplitude = amplitude_normal * amplitude_strength
|
||||
* amplitude * (1.0 + (mouse.y - 0.5) * 0.2);
|
||||
|
||||
float time_scaled = time / 10.0 + (mouse.x - 0.5) * 1.0;
|
||||
float blur = smoothstep(split_point, split_point + 0.05, st.x) * perc;
|
||||
|
||||
float xnoise = mix(
|
||||
Perlin2D(vec2(time_scaled, st.x + perc) * 2.5),
|
||||
Perlin2D(vec2(time_scaled, st.x + time_scaled) * 3.5) / 1.5,
|
||||
st.x * 0.3
|
||||
);
|
||||
|
||||
float y = 0.5 + (perc - 0.5) * distance + xnoise / 2.0 * finalAmplitude;
|
||||
|
||||
float line_start = smoothstep(
|
||||
y + (width / 2.0) + (u_line_blur * pixel(1.0, iResolution.xy) * blur),
|
||||
y,
|
||||
st.y
|
||||
);
|
||||
|
||||
float line_end = smoothstep(
|
||||
y,
|
||||
y - (width / 2.0) - (u_line_blur * pixel(1.0, iResolution.xy) * blur),
|
||||
st.y
|
||||
);
|
||||
|
||||
return clamp(
|
||||
(line_start - line_end) * (1.0 - smoothstep(0.0, 1.0, pow(perc, 0.3))),
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
|
||||
float line_strength = 1.0;
|
||||
for (int i = 0; i < u_line_count; i++) {
|
||||
float p = float(i) / float(u_line_count);
|
||||
line_strength *= (1.0 - lineFn(
|
||||
uv,
|
||||
u_line_width * pixel(1.0, iResolution.xy) * (1.0 - p),
|
||||
p,
|
||||
(PI * 1.0) * p,
|
||||
uMouse,
|
||||
iTime,
|
||||
uAmplitude,
|
||||
uDistance
|
||||
));
|
||||
}
|
||||
|
||||
float colorVal = 1.0 - line_strength;
|
||||
fragColor = vec4(uColor * colorVal, colorVal);
|
||||
}
|
||||
|
||||
void main() {
|
||||
mainImage(gl_FragColor, gl_FragCoord.xy);
|
||||
}
|
||||
`
|
||||
|
||||
const resize = () => {
|
||||
if (!containerRef.value || !renderer || !program) return
|
||||
|
||||
const container = containerRef.value
|
||||
const { clientWidth, clientHeight } = container
|
||||
renderer.setSize(clientWidth, clientHeight)
|
||||
program.uniforms.iResolution.value.r = clientWidth
|
||||
program.uniforms.iResolution.value.g = clientHeight
|
||||
program.uniforms.iResolution.value.b = clientWidth / clientHeight
|
||||
}
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!containerRef.value) return
|
||||
|
||||
const rect = containerRef.value.getBoundingClientRect()
|
||||
const x = (e.clientX - rect.left) / rect.width
|
||||
const y = 1.0 - (e.clientY - rect.top) / rect.height
|
||||
targetMouse = [x, y]
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
targetMouse = [0.5, 0.5]
|
||||
}
|
||||
|
||||
const update = (t: number) => {
|
||||
if (!program || !renderer || !mesh) return
|
||||
|
||||
if (props.enableMouseInteraction) {
|
||||
const smoothing = 0.05
|
||||
currentMouse[0] += smoothing * (targetMouse[0] - currentMouse[0])
|
||||
currentMouse[1] += smoothing * (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
|
||||
}
|
||||
|
||||
program.uniforms.iTime.value = t * 0.001
|
||||
renderer.render({ scene: mesh })
|
||||
animationId = requestAnimationFrame(update)
|
||||
}
|
||||
|
||||
const initializeScene = () => {
|
||||
if (!containerRef.value) return
|
||||
|
||||
cleanup()
|
||||
|
||||
const container = containerRef.value
|
||||
renderer = new Renderer({ alpha: true })
|
||||
gl = renderer.gl
|
||||
gl.clearColor(0, 0, 0, 0)
|
||||
gl.enable(gl.BLEND)
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
const geometry = new Triangle(gl)
|
||||
program = new Program(gl, {
|
||||
vertex: vertexShader,
|
||||
fragment: fragmentShader,
|
||||
uniforms: {
|
||||
iTime: { value: 0 },
|
||||
iResolution: {
|
||||
value: new Color(
|
||||
gl.canvas.width,
|
||||
gl.canvas.height,
|
||||
gl.canvas.width / gl.canvas.height
|
||||
)
|
||||
},
|
||||
uColor: { value: new Color(...props.color) },
|
||||
uAmplitude: { value: props.amplitude },
|
||||
uDistance: { value: props.distance },
|
||||
uMouse: { value: new Float32Array([0.5, 0.5]) }
|
||||
}
|
||||
})
|
||||
|
||||
mesh = new Mesh(gl, { geometry, program })
|
||||
|
||||
const canvas = gl.canvas as HTMLCanvasElement
|
||||
canvas.style.width = '100%'
|
||||
canvas.style.height = '100%'
|
||||
canvas.style.display = 'block'
|
||||
|
||||
container.appendChild(canvas)
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
if (props.enableMouseInteraction) {
|
||||
container.addEventListener('mousemove', handleMouseMove)
|
||||
container.addEventListener('mouseleave', handleMouseLeave)
|
||||
}
|
||||
|
||||
resize()
|
||||
animationId = requestAnimationFrame(update)
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
animationId = null
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', resize)
|
||||
|
||||
if (containerRef.value) {
|
||||
containerRef.value.removeEventListener('mousemove', handleMouseMove)
|
||||
containerRef.value.removeEventListener('mouseleave', handleMouseLeave)
|
||||
|
||||
const canvas = containerRef.value.querySelector('canvas')
|
||||
if (canvas) {
|
||||
containerRef.value.removeChild(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
if (gl) {
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext()
|
||||
}
|
||||
|
||||
renderer = null
|
||||
gl = null
|
||||
program = null
|
||||
mesh = null
|
||||
currentMouse = [0.5, 0.5]
|
||||
targetMouse = [0.5, 0.5]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initializeScene()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
watch(
|
||||
[() => props.color, () => props.amplitude, () => props.distance, () => props.enableMouseInteraction],
|
||||
() => {
|
||||
initializeScene()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
443
src/content/Backgrounds/Waves/Waves.vue
Normal file
443
src/content/Backgrounds/Waves/Waves.vue
Normal file
@@ -0,0 +1,443 @@
|
||||
<template>
|
||||
<div ref="containerRef" :class="className" :style="{ backgroundColor, ...style }"
|
||||
class="absolute top-0 left-0 w-full h-full overflow-hidden">
|
||||
<div class="absolute top-0 left-0 bg-[#160000] rounded-full w-[0.5rem] h-[0.5rem]" :style="{
|
||||
transform: 'translate3d(calc(var(--x) - 50%), calc(var(--y) - 50%), 0)',
|
||||
willChange: 'transform',
|
||||
}" />
|
||||
<canvas ref="canvasRef" class="block w-full h-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, type CSSProperties } from 'vue'
|
||||
|
||||
class Grad {
|
||||
x: number
|
||||
y: number
|
||||
z: number
|
||||
|
||||
constructor(x: number, y: number, z: number) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.z = z
|
||||
}
|
||||
|
||||
dot2(x: number, y: number): number {
|
||||
return this.x * x + this.y * y
|
||||
}
|
||||
}
|
||||
|
||||
class Noise {
|
||||
grad3: Grad[]
|
||||
p: number[]
|
||||
perm: number[]
|
||||
gradP: Grad[]
|
||||
|
||||
constructor(seed = 0) {
|
||||
this.grad3 = [
|
||||
new Grad(1, 1, 0),
|
||||
new Grad(-1, 1, 0),
|
||||
new Grad(1, -1, 0),
|
||||
new Grad(-1, -1, 0),
|
||||
new Grad(1, 0, 1),
|
||||
new Grad(-1, 0, 1),
|
||||
new Grad(1, 0, -1),
|
||||
new Grad(-1, 0, -1),
|
||||
new Grad(0, 1, 1),
|
||||
new Grad(0, -1, 1),
|
||||
new Grad(0, 1, -1),
|
||||
new Grad(0, -1, -1),
|
||||
]
|
||||
|
||||
this.p = [
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
|
||||
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247,
|
||||
120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177,
|
||||
33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165,
|
||||
71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211,
|
||||
133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25,
|
||||
63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196,
|
||||
135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217,
|
||||
226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206,
|
||||
59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248,
|
||||
152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22,
|
||||
39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218,
|
||||
246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
|
||||
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
|
||||
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
|
||||
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
|
||||
]
|
||||
|
||||
this.perm = new Array(512)
|
||||
this.gradP = new Array(512)
|
||||
this.seed(seed)
|
||||
}
|
||||
|
||||
seed(seed: number) {
|
||||
if (seed > 0 && seed < 1) seed *= 65536
|
||||
seed = Math.floor(seed)
|
||||
if (seed < 256) seed |= seed << 8
|
||||
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const v = i & 1 ? this.p[i] ^ (seed & 255) : this.p[i] ^ ((seed >> 8) & 255)
|
||||
this.perm[i] = this.perm[i + 256] = v
|
||||
this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12]
|
||||
}
|
||||
}
|
||||
|
||||
fade(t: number): number {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
}
|
||||
|
||||
lerp(a: number, b: number, t: number): number {
|
||||
return (1 - t) * a + t * b
|
||||
}
|
||||
|
||||
perlin2(x: number, y: number): number {
|
||||
let X = Math.floor(x),
|
||||
Y = Math.floor(y)
|
||||
x -= X
|
||||
y -= Y
|
||||
X &= 255
|
||||
Y &= 255
|
||||
|
||||
const n00 = this.gradP[X + this.perm[Y]].dot2(x, y)
|
||||
const n01 = this.gradP[X + this.perm[Y + 1]].dot2(x, y - 1)
|
||||
const n10 = this.gradP[X + 1 + this.perm[Y]].dot2(x - 1, y)
|
||||
const n11 = this.gradP[X + 1 + this.perm[Y + 1]].dot2(x - 1, y - 1)
|
||||
const u = this.fade(x)
|
||||
|
||||
return this.lerp(
|
||||
this.lerp(n00, n10, u),
|
||||
this.lerp(n01, n11, u),
|
||||
this.fade(y)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number
|
||||
y: number
|
||||
wave: { x: number; y: number }
|
||||
cursor: { x: number; y: number; vx: number; vy: number }
|
||||
}
|
||||
|
||||
interface Mouse {
|
||||
x: number
|
||||
y: number
|
||||
lx: number
|
||||
ly: number
|
||||
sx: number
|
||||
sy: number
|
||||
v: number
|
||||
vs: number
|
||||
a: number
|
||||
set: boolean
|
||||
}
|
||||
|
||||
interface Config {
|
||||
lineColor: string
|
||||
waveSpeedX: number
|
||||
waveSpeedY: number
|
||||
waveAmpX: number
|
||||
waveAmpY: number
|
||||
friction: number
|
||||
tension: number
|
||||
maxCursorMove: number
|
||||
xGap: number
|
||||
yGap: number
|
||||
}
|
||||
|
||||
interface WavesProps {
|
||||
lineColor?: string
|
||||
backgroundColor?: string
|
||||
waveSpeedX?: number
|
||||
waveSpeedY?: number
|
||||
waveAmpX?: number
|
||||
waveAmpY?: number
|
||||
xGap?: number
|
||||
yGap?: number
|
||||
friction?: number
|
||||
tension?: number
|
||||
maxCursorMove?: number
|
||||
style?: CSSProperties
|
||||
className?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<WavesProps>(), {
|
||||
lineColor: 'black',
|
||||
backgroundColor: 'transparent',
|
||||
waveSpeedX: 0.0125,
|
||||
waveSpeedY: 0.005,
|
||||
waveAmpX: 32,
|
||||
waveAmpY: 16,
|
||||
xGap: 10,
|
||||
yGap: 32,
|
||||
friction: 0.925,
|
||||
tension: 0.005,
|
||||
maxCursorMove: 100,
|
||||
style: () => ({}),
|
||||
className: ''
|
||||
})
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
let bounding = { width: 0, height: 0, left: 0, top: 0 }
|
||||
let noise: Noise | null = null
|
||||
let lines: Point[][] = []
|
||||
const mouse: Mouse = {
|
||||
x: -10,
|
||||
y: 0,
|
||||
lx: 0,
|
||||
ly: 0,
|
||||
sx: 0,
|
||||
sy: 0,
|
||||
v: 0,
|
||||
vs: 0,
|
||||
a: 0,
|
||||
set: false,
|
||||
}
|
||||
let config: Config = {
|
||||
lineColor: props.lineColor,
|
||||
waveSpeedX: props.waveSpeedX,
|
||||
waveSpeedY: props.waveSpeedY,
|
||||
waveAmpX: props.waveAmpX,
|
||||
waveAmpY: props.waveAmpY,
|
||||
friction: props.friction,
|
||||
tension: props.tension,
|
||||
maxCursorMove: props.maxCursorMove,
|
||||
xGap: props.xGap,
|
||||
yGap: props.yGap,
|
||||
}
|
||||
let frameId: number | null = null
|
||||
|
||||
const setSize = () => {
|
||||
const container = containerRef.value
|
||||
const canvas = canvasRef.value
|
||||
if (!container || !canvas) return
|
||||
|
||||
const rect = container.getBoundingClientRect()
|
||||
bounding = {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
}
|
||||
canvas.width = rect.width
|
||||
canvas.height = rect.height
|
||||
}
|
||||
|
||||
const setLines = () => {
|
||||
const { width, height } = bounding
|
||||
lines = []
|
||||
const oWidth = width + 200,
|
||||
oHeight = height + 30
|
||||
const { xGap, yGap } = config
|
||||
const totalLines = Math.ceil(oWidth / xGap)
|
||||
const totalPoints = Math.ceil(oHeight / yGap)
|
||||
const xStart = (width - xGap * totalLines) / 2
|
||||
const yStart = (height - yGap * totalPoints) / 2
|
||||
|
||||
for (let i = 0; i <= totalLines; i++) {
|
||||
const pts: Point[] = []
|
||||
for (let j = 0; j <= totalPoints; j++) {
|
||||
pts.push({
|
||||
x: xStart + xGap * i,
|
||||
y: yStart + yGap * j,
|
||||
wave: { x: 0, y: 0 },
|
||||
cursor: { x: 0, y: 0, vx: 0, vy: 0 },
|
||||
})
|
||||
}
|
||||
lines.push(pts)
|
||||
}
|
||||
}
|
||||
|
||||
const movePoints = (time: number) => {
|
||||
if (!noise) return
|
||||
|
||||
const {
|
||||
waveSpeedX,
|
||||
waveSpeedY,
|
||||
waveAmpX,
|
||||
waveAmpY,
|
||||
friction,
|
||||
tension,
|
||||
maxCursorMove,
|
||||
} = config
|
||||
|
||||
lines.forEach((pts) => {
|
||||
pts.forEach((p) => {
|
||||
const move = noise!.perlin2(
|
||||
(p.x + time * waveSpeedX) * 0.002,
|
||||
(p.y + time * waveSpeedY) * 0.0015
|
||||
) * 12
|
||||
p.wave.x = Math.cos(move) * waveAmpX
|
||||
p.wave.y = Math.sin(move) * waveAmpY
|
||||
|
||||
const dx = p.x - mouse.sx,
|
||||
dy = p.y - mouse.sy
|
||||
const dist = Math.hypot(dx, dy)
|
||||
const l = Math.max(175, mouse.vs)
|
||||
if (dist < l) {
|
||||
const s = 1 - dist / l
|
||||
const f = Math.cos(dist * 0.001) * s
|
||||
p.cursor.vx += Math.cos(mouse.a) * f * l * mouse.vs * 0.00065
|
||||
p.cursor.vy += Math.sin(mouse.a) * f * l * mouse.vs * 0.00065
|
||||
}
|
||||
|
||||
p.cursor.vx += (0 - p.cursor.x) * tension
|
||||
p.cursor.vy += (0 - p.cursor.y) * tension
|
||||
p.cursor.vx *= friction
|
||||
p.cursor.vy *= friction
|
||||
p.cursor.x += p.cursor.vx * 2
|
||||
p.cursor.y += p.cursor.vy * 2
|
||||
p.cursor.x = Math.min(
|
||||
maxCursorMove,
|
||||
Math.max(-maxCursorMove, p.cursor.x)
|
||||
)
|
||||
p.cursor.y = Math.min(
|
||||
maxCursorMove,
|
||||
Math.max(-maxCursorMove, p.cursor.y)
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const moved = (point: Point, withCursor = true): { x: number; y: number } => {
|
||||
const x = point.x + point.wave.x + (withCursor ? point.cursor.x : 0)
|
||||
const y = point.y + point.wave.y + (withCursor ? point.cursor.y : 0)
|
||||
return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 }
|
||||
}
|
||||
|
||||
const drawLines = () => {
|
||||
const { width, height } = bounding
|
||||
if (!ctx) return
|
||||
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = config.lineColor
|
||||
|
||||
lines.forEach((points) => {
|
||||
let p1 = moved(points[0], false)
|
||||
ctx!.moveTo(p1.x, p1.y)
|
||||
points.forEach((p, idx) => {
|
||||
const isLast = idx === points.length - 1
|
||||
p1 = moved(p, !isLast)
|
||||
const p2 = moved(
|
||||
points[idx + 1] || points[points.length - 1],
|
||||
!isLast
|
||||
)
|
||||
ctx!.lineTo(p1.x, p1.y)
|
||||
if (isLast) ctx!.moveTo(p2.x, p2.y)
|
||||
})
|
||||
})
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
const tick = (t: number) => {
|
||||
const container = containerRef.value
|
||||
if (!container) return
|
||||
|
||||
mouse.sx += (mouse.x - mouse.sx) * 0.1
|
||||
mouse.sy += (mouse.y - mouse.sy) * 0.1
|
||||
const dx = mouse.x - mouse.lx,
|
||||
dy = mouse.y - mouse.ly
|
||||
const d = Math.hypot(dx, dy)
|
||||
mouse.v = d
|
||||
mouse.vs += (d - mouse.vs) * 0.1
|
||||
mouse.vs = Math.min(100, mouse.vs)
|
||||
mouse.lx = mouse.x
|
||||
mouse.ly = mouse.y
|
||||
mouse.a = Math.atan2(dy, dx)
|
||||
container.style.setProperty('--x', `${mouse.sx}px`)
|
||||
container.style.setProperty('--y', `${mouse.sy}px`)
|
||||
|
||||
movePoints(t)
|
||||
drawLines()
|
||||
frameId = requestAnimationFrame(tick)
|
||||
}
|
||||
|
||||
const onResize = () => {
|
||||
setSize()
|
||||
setLines()
|
||||
}
|
||||
|
||||
const updateMouse = (x: number, y: number) => {
|
||||
mouse.x = x - bounding.left
|
||||
mouse.y = y - bounding.top
|
||||
if (!mouse.set) {
|
||||
mouse.sx = mouse.x
|
||||
mouse.sy = mouse.y
|
||||
mouse.lx = mouse.x
|
||||
mouse.ly = mouse.y
|
||||
mouse.set = true
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
updateMouse(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
const onTouchMove = (e: TouchEvent) => {
|
||||
const touch = e.touches[0]
|
||||
updateMouse(touch.clientX, touch.clientY)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const canvas = canvasRef.value
|
||||
const container = containerRef.value
|
||||
if (!canvas || !container) return
|
||||
|
||||
ctx = canvas.getContext('2d')
|
||||
noise = new Noise(Math.random())
|
||||
|
||||
setSize()
|
||||
setLines()
|
||||
frameId = requestAnimationFrame(tick)
|
||||
|
||||
window.addEventListener('resize', onResize)
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('touchmove', onTouchMove, { passive: false })
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onResize)
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
window.removeEventListener('touchmove', onTouchMove)
|
||||
if (frameId !== null) {
|
||||
cancelAnimationFrame(frameId)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [
|
||||
props.lineColor,
|
||||
props.waveSpeedX,
|
||||
props.waveSpeedY,
|
||||
props.waveAmpX,
|
||||
props.waveAmpY,
|
||||
props.friction,
|
||||
props.tension,
|
||||
props.maxCursorMove,
|
||||
props.xGap,
|
||||
props.yGap,
|
||||
],
|
||||
() => {
|
||||
config = {
|
||||
lineColor: props.lineColor,
|
||||
waveSpeedX: props.waveSpeedX,
|
||||
waveSpeedY: props.waveSpeedY,
|
||||
waveAmpX: props.waveAmpX,
|
||||
waveAmpY: props.waveAmpY,
|
||||
friction: props.friction,
|
||||
tension: props.tension,
|
||||
maxCursorMove: props.maxCursorMove,
|
||||
xGap: props.xGap,
|
||||
yGap: props.yGap,
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
Reference in New Issue
Block a user