This commit is contained in:
@@ -6,6 +6,10 @@ let ctx: CanvasRenderingContext2D | null = null
|
|||||||
let animId: number
|
let animId: number
|
||||||
let particles: Particle[] = []
|
let particles: Particle[] = []
|
||||||
let accentColor: string = ''
|
let accentColor: string = ''
|
||||||
|
let scrollY: number = 0
|
||||||
|
let mouseX: number = -9999
|
||||||
|
let mouseY: number = -9999
|
||||||
|
let bgParticles: Particle[] = []
|
||||||
|
|
||||||
interface Particle {
|
interface Particle {
|
||||||
x: number
|
x: number
|
||||||
@@ -13,6 +17,7 @@ interface Particle {
|
|||||||
vx: number
|
vx: number
|
||||||
vy: number
|
vy: number
|
||||||
size: number
|
size: number
|
||||||
|
parallax: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAccent(hex: string): [number, number, number] {
|
function parseAccent(hex: string): [number, number, number] {
|
||||||
@@ -22,9 +27,16 @@ function parseAccent(hex: string): [number, number, number] {
|
|||||||
return [r, g, b]
|
return [r, g, b]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseBgAccent(hex: string): [number, number, number] {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16)
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16)
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16)
|
||||||
|
return [r, g, b]
|
||||||
|
}
|
||||||
|
|
||||||
function createParticles(w: number, h: number): Particle[] {
|
function createParticles(w: number, h: number): Particle[] {
|
||||||
const count = Math.floor((w * h) / 40000)
|
const count = Math.floor((w * h) / 30000)
|
||||||
const capped = Math.min(count, 80)
|
const capped = Math.min(count, 70)
|
||||||
const particlesArr: Particle[] = []
|
const particlesArr: Particle[] = []
|
||||||
|
|
||||||
for (let i = 0; i < capped; i++) {
|
for (let i = 0; i < capped; i++) {
|
||||||
@@ -33,7 +45,27 @@ function createParticles(w: number, h: number): Particle[] {
|
|||||||
y: Math.random() * h,
|
y: Math.random() * h,
|
||||||
vx: (Math.random() - 0.5) * 0.3,
|
vx: (Math.random() - 0.5) * 0.3,
|
||||||
vy: (Math.random() - 0.5) * 0.3,
|
vy: (Math.random() - 0.5) * 0.3,
|
||||||
size: Math.random() * 2 + 1,
|
size: 5,
|
||||||
|
parallax: 0.15,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return particlesArr
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBgParticles(w: number, h: number): Particle[] {
|
||||||
|
const count = Math.floor((w * h) / 8000)
|
||||||
|
const capped = Math.min(count, 400)
|
||||||
|
const particlesArr: Particle[] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < capped; i++) {
|
||||||
|
particlesArr.push({
|
||||||
|
x: Math.random() * w,
|
||||||
|
y: Math.random() * h,
|
||||||
|
vx: (Math.random() - 0.5) * 0.15,
|
||||||
|
vy: (Math.random() - 0.5) * 0.15,
|
||||||
|
size: 3,
|
||||||
|
parallax: 0.03,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,14 +80,22 @@ function drawConnections(w: number, h: number) {
|
|||||||
const a = particles[i]
|
const a = particles[i]
|
||||||
for (let j = i + 1; j < particles.length; j++) {
|
for (let j = i + 1; j < particles.length; j++) {
|
||||||
const b2 = particles[j]
|
const b2 = particles[j]
|
||||||
|
const ay = (a as any)._dy ?? a.y
|
||||||
|
const by = (b2 as any)._dy ?? b2.y
|
||||||
const dx = a.x - b2.x
|
const dx = a.x - b2.x
|
||||||
const dy = a.y - b2.y
|
const dy = ay - by
|
||||||
const dist = Math.sqrt(dx * dx + dy * dy)
|
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||||
if (dist < 100) {
|
if (dist < 100) {
|
||||||
const connAlpha = (1 - dist / 100) * 0.25
|
const connAlpha = (1 - dist / 100) * 0.25
|
||||||
|
const offset = Math.ceil(Math.max(a.size, b2.size))
|
||||||
|
const angle = Math.atan2(dy, dx)
|
||||||
|
const edgeA_x = a.x + Math.cos(angle) * offset
|
||||||
|
const edgeA_y = ay + Math.sin(angle) * offset
|
||||||
|
const edgeB_x = b2.x - Math.cos(angle) * offset
|
||||||
|
const edgeB_y = by - Math.sin(angle) * offset
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(a.x, a.y)
|
ctx.moveTo(edgeA_x, edgeA_y)
|
||||||
ctx.lineTo(b2.x, b2.y)
|
ctx.lineTo(edgeB_x, edgeB_y)
|
||||||
ctx.strokeStyle = `rgba(${r},${g},${b},${connAlpha})`
|
ctx.strokeStyle = `rgba(${r},${g},${b},${connAlpha})`
|
||||||
ctx.lineWidth = 1
|
ctx.lineWidth = 1
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
@@ -66,15 +106,17 @@ function drawConnections(w: number, h: number) {
|
|||||||
|
|
||||||
function drawTuiParticle(x: number, y: number, size: number, r: number, g: number, b: number) {
|
function drawTuiParticle(x: number, y: number, size: number, r: number, g: number, b: number) {
|
||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
const half = Math.ceil(size)
|
|
||||||
|
|
||||||
// Draw plus/cross shape (TUI cursor style)
|
// Draw plus/cross shape (TUI cursor style)
|
||||||
ctx.fillStyle = `rgba(${r},${g},${b},0.85)`
|
ctx.fillStyle = `rgba(${r},${g},${b},0.85)`
|
||||||
ctx.fillRect(Math.round(x - half), y - 1, size, 1)
|
ctx.fillRect(Math.round(x - size / 2), Math.round(y - size / 2), size, size)
|
||||||
ctx.fillRect(x - 1, Math.round(y - half), 1, size)
|
}
|
||||||
|
|
||||||
// Center dot
|
function drawBgParticle(x: number, y: number, size: number, r: number, g: number, b: number) {
|
||||||
ctx.fillRect(Math.round(x) - 1, Math.round(y) - 1, 2, 2)
|
if (!ctx) return
|
||||||
|
|
||||||
|
ctx.fillStyle = `rgba(${r},${g},${b},0.3)`
|
||||||
|
ctx.fillRect(Math.round(x - size / 2), Math.round(y - size / 2), size, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
function animate(): void {
|
function animate(): void {
|
||||||
@@ -87,6 +129,28 @@ function animate(): void {
|
|||||||
|
|
||||||
|
|
||||||
for (const p of particles) {
|
for (const p of particles) {
|
||||||
|
const drawY = p.y - scrollY * p.parallax
|
||||||
|
const dx = p.x - mouseX
|
||||||
|
const dy = drawY - mouseY
|
||||||
|
const dist2 = dx * dx + dy * dy
|
||||||
|
if (dist2 < 6400 && dist2 > 1) {
|
||||||
|
const dist = Math.sqrt(dist2)
|
||||||
|
const force = 0.8 / dist
|
||||||
|
p.vx += (dx / dist) * force
|
||||||
|
p.vy += (dy / dist) * force
|
||||||
|
}
|
||||||
|
|
||||||
|
p.vx *= 0.98
|
||||||
|
p.vy *= 0.98
|
||||||
|
|
||||||
|
const minSpeed = 0.15
|
||||||
|
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy)
|
||||||
|
if (speed < minSpeed) {
|
||||||
|
const angle = Math.atan2((Math.random() - 0.5) * 0.3, (Math.random() - 0.5) * 0.3)
|
||||||
|
p.vx = Math.cos(angle) * minSpeed
|
||||||
|
p.vy = Math.sin(angle) * minSpeed
|
||||||
|
}
|
||||||
|
|
||||||
p.x += p.vx
|
p.x += p.vx
|
||||||
p.y += p.vy
|
p.y += p.vy
|
||||||
if (p.x < -20) p.x = w + 20
|
if (p.x < -20) p.x = w + 20
|
||||||
@@ -94,10 +158,51 @@ function animate(): void {
|
|||||||
if (p.y < -20) p.y = h + 20
|
if (p.y < -20) p.y = h + 20
|
||||||
if (p.y > h + 20) p.y = -20
|
if (p.y > h + 20) p.y = -20
|
||||||
|
|
||||||
drawTuiParticle(p.x, p.y, p.size, r, g, b)
|
p._dy = drawY
|
||||||
|
drawTuiParticle(p.x, p._dy, p.size, r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawConnections(w, h)
|
drawConnections(w, h)
|
||||||
|
|
||||||
|
const bgColor = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--color-border-color')
|
||||||
|
.trim()
|
||||||
|
if (bgColor && bgColor.startsWith('#')) {
|
||||||
|
const [br, bg, bb] = parseBgAccent(bgColor)
|
||||||
|
for (const p of bgParticles) {
|
||||||
|
const dy = (p as any)._dy ?? p.y - scrollY * p.parallax
|
||||||
|
const dx = p.x - mouseX
|
||||||
|
const dist2 = dx * dx + dy * dy
|
||||||
|
if (dist2 < 6400 && dist2 > 1) {
|
||||||
|
const dist = Math.sqrt(dist2)
|
||||||
|
const force = 0.3 / dist
|
||||||
|
p.vx += (dx / dist) * force
|
||||||
|
p.vy += (dy / dist) * force
|
||||||
|
}
|
||||||
|
|
||||||
|
p.vx *= 0.99
|
||||||
|
p.vy *= 0.99
|
||||||
|
|
||||||
|
const minSpeed = 0.1
|
||||||
|
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy)
|
||||||
|
if (speed < minSpeed) {
|
||||||
|
const angle = Math.atan2((Math.random() - 0.5) * 0.15, (Math.random() - 0.5) * 0.15)
|
||||||
|
p.vx = Math.cos(angle) * minSpeed
|
||||||
|
p.vy = Math.sin(angle) * minSpeed
|
||||||
|
}
|
||||||
|
|
||||||
|
p.x += p.vx
|
||||||
|
p.y += p.vy
|
||||||
|
if (p.x < -10) p.x = w + 10
|
||||||
|
if (p.x > w + 10) p.x = -10
|
||||||
|
if (p.y < -10) p.y = h + 10
|
||||||
|
if (p.y > h + 10) p.y = -10
|
||||||
|
|
||||||
|
const drawY = p.y - scrollY * p.parallax
|
||||||
|
drawBgParticle(p.x, drawY, p.size, br, bg, bb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
animId = requestAnimationFrame(animate)
|
animId = requestAnimationFrame(animate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +227,7 @@ function initCanvas() {
|
|||||||
|
|
||||||
if (ctx) ctx.scale(dpr, dpr)
|
if (ctx) ctx.scale(dpr, dpr)
|
||||||
particles = createParticles(w, h)
|
particles = createParticles(w, h)
|
||||||
|
bgParticles = createBgParticles(w, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -155,11 +261,21 @@ onMounted(() => {
|
|||||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||||
ctx.scale(dpr, dpr)
|
ctx.scale(dpr, dpr)
|
||||||
particles = createParticles(w, h)
|
particles = createParticles(w, h)
|
||||||
|
bgParticles = createBgParticles(w, h)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (ctx && canvas.value) {
|
if (ctx && canvas.value) {
|
||||||
animate()
|
animate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
scrollY = window.scrollY
|
||||||
|
}, { passive: true })
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', (e) => {
|
||||||
|
mouseX = e.clientX
|
||||||
|
mouseY = e.clientY
|
||||||
|
}, { passive: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user