增加背景样式种类
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<canvas ref="canvas" class="bg-canvas"></canvas>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
let width = 0
|
||||
let height = 0
|
||||
let dpr = 1
|
||||
let raf = 0
|
||||
let running = true
|
||||
|
||||
type Particle = {
|
||||
baseX: number
|
||||
baseY: number
|
||||
r: number
|
||||
vx: number
|
||||
vy: number
|
||||
speed: number
|
||||
phase: number
|
||||
depth: number
|
||||
alpha: number
|
||||
}
|
||||
|
||||
let particles: Particle[] = []
|
||||
let mouseX = 0
|
||||
let mouseY = 0
|
||||
let lastThemeKey = ''
|
||||
|
||||
function parseColor(c: string): [number, number, number] {
|
||||
const s = c.trim()
|
||||
if (s.startsWith('#')) {
|
||||
const v = s.slice(1)
|
||||
const n = v.length === 3 ? v.split('').map(ch => ch + ch).join('') : v
|
||||
const r = parseInt(n.slice(0, 2), 16)
|
||||
const g = parseInt(n.slice(2, 4), 16)
|
||||
const b = parseInt(n.slice(4, 6), 16)
|
||||
return [r, g, b]
|
||||
}
|
||||
const m = s.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i)
|
||||
if (m) return [Number(m[1]), Number(m[2]), Number(m[3])]
|
||||
return [219, 234, 254]
|
||||
}
|
||||
|
||||
function mix(a: [number, number, number], b: [number, number, number], t: number): [number, number, number] {
|
||||
return [
|
||||
Math.round(a[0] * (1 - t) + b[0] * t),
|
||||
Math.round(a[1] * (1 - t) + b[1] * t),
|
||||
Math.round(a[2] * (1 - t) + b[2] * t)
|
||||
]
|
||||
}
|
||||
|
||||
function luminance([r, g, b]: [number, number, number]) {
|
||||
const sr = r / 255
|
||||
const sg = g / 255
|
||||
const sb = b / 255
|
||||
return 0.2126 * sr + 0.7152 * sg + 0.0722 * sb
|
||||
}
|
||||
|
||||
function getPalette() {
|
||||
const cs = getComputedStyle(document.documentElement)
|
||||
const accent = cs.getPropertyValue('--color-accent-light') || '#dbeafe'
|
||||
const bg = cs.getPropertyValue('--color-bg-primary') || '#ffffff'
|
||||
const a = parseColor(accent)
|
||||
const b = parseColor(bg)
|
||||
const mixed = mix(b, a, 3)
|
||||
const key = `${mixed.join(',')}`
|
||||
const lum = luminance(b)
|
||||
const alpha = lum > 0.7 ? 0.42 : 0.28
|
||||
return { rgb: mixed, key, alpha }
|
||||
}
|
||||
|
||||
function resize() {
|
||||
if (!canvas.value) return
|
||||
dpr = Math.min(window.devicePixelRatio || 1, 2)
|
||||
width = window.innerWidth
|
||||
height = window.innerHeight
|
||||
canvas.value.width = Math.floor(width * dpr)
|
||||
canvas.value.height = Math.floor(height * dpr)
|
||||
canvas.value.style.width = `${width}px`
|
||||
canvas.value.style.height = `${height}px`
|
||||
}
|
||||
|
||||
function initParticles() {
|
||||
const area = width * height
|
||||
const count = Math.max(80, Math.floor(area / (60 * 60)))
|
||||
particles = Array.from({ length: count }).map(() => {
|
||||
const depth = 0.35 + Math.random() * 0.65
|
||||
const r = 0.9 + Math.random() * 1.6
|
||||
const speed = 0.4 + Math.random() * 0.8
|
||||
const vx = (Math.random() - 0.5) * 0.6
|
||||
const vy = (Math.random() - 0.5) * 0.6
|
||||
return {
|
||||
baseX: Math.random() * width,
|
||||
baseY: Math.random() * height,
|
||||
r,
|
||||
vx,
|
||||
vy,
|
||||
speed,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
depth,
|
||||
alpha: 0.7 + Math.random() * 0.3
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!ctx) return
|
||||
const pal = getPalette()
|
||||
if (pal.key !== lastThemeKey) lastThemeKey = pal.key
|
||||
ctx.clearRect(0, 0, width * dpr, height * dpr)
|
||||
ctx.globalCompositeOperation = 'source-over'
|
||||
const [r, g, b] = pal.rgb
|
||||
const t = performance.now() / 1000
|
||||
const mx = (mouseX / width - 0.5)
|
||||
const my = (mouseY / height - 0.5)
|
||||
const offsetX = mx * 160
|
||||
const offsetY = my * 100
|
||||
for (const p of particles) {
|
||||
const x = (p.baseX + Math.sin(t * p.speed + p.phase) * p.vx * 50 + offsetX * p.depth) * dpr
|
||||
const y = (p.baseY + Math.cos(t * p.speed + p.phase) * p.vy * 50 + offsetY * p.depth) * dpr
|
||||
const a = pal.alpha * p.alpha * (0.85 + 0.15 * Math.sin(t * (p.speed * 0.6) + p.phase))
|
||||
ctx.fillStyle = `rgba(${r},${g},${b},${a})`
|
||||
ctx.beginPath()
|
||||
ctx.arc(x, y, Math.max(0.5, p.r * dpr), 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
let lastFrame = 0
|
||||
|
||||
function loop(ts: number) {
|
||||
if (!running) return
|
||||
if (ts - lastFrame > 33) {
|
||||
lastFrame = ts
|
||||
draw()
|
||||
}
|
||||
raf = requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
mouseX = e.clientX
|
||||
mouseY = e.clientY
|
||||
}
|
||||
|
||||
function onVisibilityChange() {
|
||||
running = !document.hidden
|
||||
if (running) {
|
||||
lastFrame = 0
|
||||
raf = requestAnimationFrame(loop)
|
||||
} else {
|
||||
cancelAnimationFrame(raf)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!canvas.value) return
|
||||
ctx = canvas.value.getContext('2d')
|
||||
resize()
|
||||
initParticles()
|
||||
window.addEventListener('resize', () => {
|
||||
resize()
|
||||
initParticles()
|
||||
})
|
||||
window.addEventListener('mousemove', onMouseMove, { passive: true })
|
||||
document.addEventListener('visibilitychange', onVisibilityChange)
|
||||
raf = requestAnimationFrame(loop)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
running = false
|
||||
cancelAnimationFrame(raf)
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('visibilitychange', onVisibilityChange)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.bg-canvas {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<canvas ref="canvas" class="bg-gradient"></canvas>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
let width = 0
|
||||
let height = 0
|
||||
let raf = 0
|
||||
let running = true
|
||||
|
||||
// Mouse interaction
|
||||
const mouse = { x: 0, y: 0, tx: 0, ty: 0 }
|
||||
|
||||
function parseColor(c: string): [number, number, number] {
|
||||
if (!c) return [0, 0, 0]
|
||||
if (c.startsWith('#')) {
|
||||
const v = c.slice(1)
|
||||
const n = v.length === 3 ? v.split('').map(ch => ch + ch).join('') : v
|
||||
const r = parseInt(n.slice(0, 2), 16)
|
||||
const g = parseInt(n.slice(2, 4), 16)
|
||||
const b = parseInt(n.slice(4, 6), 16)
|
||||
return [r, g, b]
|
||||
}
|
||||
return [255, 255, 255]
|
||||
}
|
||||
|
||||
function getThemeColors() {
|
||||
const cs = getComputedStyle(document.documentElement)
|
||||
const accent = parseColor(cs.getPropertyValue('--color-accent').trim())
|
||||
const primary = parseColor(cs.getPropertyValue('--color-bg-primary').trim())
|
||||
|
||||
return { accent, primary }
|
||||
}
|
||||
|
||||
function resize() {
|
||||
if (!canvas.value) return
|
||||
width = window.innerWidth
|
||||
height = window.innerHeight
|
||||
canvas.value.width = width
|
||||
canvas.value.height = height
|
||||
}
|
||||
|
||||
// Mesh points
|
||||
interface Point {
|
||||
x: number
|
||||
y: number
|
||||
originX: number
|
||||
originY: number
|
||||
noiseOffsetX: number
|
||||
noiseOffsetY: number
|
||||
}
|
||||
|
||||
const points: Point[] = []
|
||||
const gridSize = 100 // Size of mesh cells
|
||||
|
||||
function initMesh() {
|
||||
points.length = 0
|
||||
const cols = Math.ceil(width / gridSize) + 2
|
||||
const rows = Math.ceil(height / gridSize) + 2
|
||||
|
||||
for (let i = 0; i < cols; i++) {
|
||||
for (let j = 0; j < rows; j++) {
|
||||
const x = (i - 1) * gridSize
|
||||
const y = (j - 1) * gridSize
|
||||
points.push({
|
||||
x,
|
||||
y,
|
||||
originX: x,
|
||||
originY: y,
|
||||
noiseOffsetX: Math.random() * 1000,
|
||||
noiseOffsetY: Math.random() * 1000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!ctx) return
|
||||
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
// Smooth mouse
|
||||
mouse.x += (mouse.tx - mouse.x) * 0.05
|
||||
mouse.y += (mouse.ty - mouse.y) * 0.05
|
||||
|
||||
const time = performance.now() / 2000
|
||||
const { accent, primary } = getThemeColors()
|
||||
|
||||
// Update points
|
||||
points.forEach(p => {
|
||||
// Simplex-like noise movement (simplified with sine/cos)
|
||||
const noiseX = Math.sin(time + p.noiseOffsetX) * 30
|
||||
const noiseY = Math.cos(time + p.noiseOffsetY) * 30
|
||||
|
||||
// Mouse influence (gentle push)
|
||||
const dx = mouse.x - p.originX
|
||||
const dy = mouse.y - p.originY
|
||||
const dist = Math.sqrt(dx*dx + dy*dy)
|
||||
const maxDist = 400
|
||||
let mouseX = 0
|
||||
let mouseY = 0
|
||||
|
||||
if (dist < maxDist) {
|
||||
const force = Math.pow(1 - dist / maxDist, 2) * 40
|
||||
const angle = Math.atan2(dy, dx)
|
||||
mouseX = Math.cos(angle) * force
|
||||
mouseY = Math.sin(angle) * force
|
||||
}
|
||||
|
||||
p.x = p.originX + noiseX - mouseX
|
||||
p.y = p.originY + noiseY - mouseY
|
||||
})
|
||||
|
||||
// Draw mesh
|
||||
ctx.lineWidth = 1
|
||||
// Create gradient for lines
|
||||
const gradient = ctx.createLinearGradient(0, 0, width, height)
|
||||
gradient.addColorStop(0, `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, 0.05)`)
|
||||
gradient.addColorStop(0.5, `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, 0.1)`)
|
||||
gradient.addColorStop(1, `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, 0.05)`)
|
||||
|
||||
ctx.strokeStyle = gradient
|
||||
|
||||
const cols = Math.ceil(width / gridSize) + 2
|
||||
const rows = Math.ceil(height / gridSize) + 2
|
||||
|
||||
// Draw connections
|
||||
ctx.beginPath()
|
||||
for (let i = 0; i < cols; i++) {
|
||||
for (let j = 0; j < rows; j++) {
|
||||
const index = i * rows + j
|
||||
const p = points[index]
|
||||
|
||||
if (i < cols - 1) {
|
||||
const right = points[(i + 1) * rows + j]
|
||||
if (p) ctx.moveTo(p.x, p.y)
|
||||
if (right) ctx.lineTo(right.x, right.y)
|
||||
}
|
||||
|
||||
if (j < rows - 1) {
|
||||
const bottom = points[i * rows + (j + 1)]
|
||||
if (p) ctx.moveTo(p.x, p.y)
|
||||
if (bottom) ctx.lineTo(bottom.x, bottom.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.stroke()
|
||||
|
||||
// Fill subtle gradient background
|
||||
const bgGradient = ctx.createRadialGradient(
|
||||
mouse.x, mouse.y, 0,
|
||||
mouse.x, mouse.y, Math.max(width, height)
|
||||
)
|
||||
bgGradient.addColorStop(0, `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, 0.03)`)
|
||||
bgGradient.addColorStop(1, 'rgba(0,0,0,0)')
|
||||
|
||||
ctx.fillStyle = bgGradient
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
}
|
||||
|
||||
function loop() {
|
||||
if (!running) return
|
||||
draw()
|
||||
raf = requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
mouse.tx = e.clientX
|
||||
mouse.ty = e.clientY
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
resize()
|
||||
initMesh()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!canvas.value) return
|
||||
ctx = canvas.value.getContext('2d')
|
||||
resize()
|
||||
initMesh()
|
||||
window.addEventListener('resize', onResize)
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
raf = requestAnimationFrame(loop)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
running = false
|
||||
cancelAnimationFrame(raf)
|
||||
window.removeEventListener('resize', onResize)
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bg-gradient {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<canvas ref="canvas" class="bg-grid"></canvas>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
let width = 0
|
||||
let height = 0
|
||||
let raf = 0
|
||||
let running = true
|
||||
|
||||
// Mouse interaction
|
||||
const mouse = { x: 0, y: 0, tx: 0, ty: 0 }
|
||||
|
||||
// Grid properties
|
||||
const gridSize = 50 // Increased grid size for cleaner look
|
||||
let offset = 0
|
||||
|
||||
function parseColor(c: string): [number, number, number] {
|
||||
if (!c) return [0, 0, 0]
|
||||
if (c.startsWith('#')) {
|
||||
const v = c.slice(1)
|
||||
const n = v.length === 3 ? v.split('').map(ch => ch + ch).join('') : v
|
||||
const r = parseInt(n.slice(0, 2), 16)
|
||||
const g = parseInt(n.slice(2, 4), 16)
|
||||
const b = parseInt(n.slice(4, 6), 16)
|
||||
return [r, g, b]
|
||||
}
|
||||
return [255, 255, 255]
|
||||
}
|
||||
|
||||
function getThemeColors() {
|
||||
const cs = getComputedStyle(document.documentElement)
|
||||
const accent = parseColor(cs.getPropertyValue('--color-accent').trim())
|
||||
const text = parseColor(cs.getPropertyValue('--color-text-primary').trim())
|
||||
|
||||
return { accent, text }
|
||||
}
|
||||
|
||||
function resize() {
|
||||
if (!canvas.value) return
|
||||
width = window.innerWidth
|
||||
height = window.innerHeight
|
||||
canvas.value.width = width
|
||||
canvas.value.height = height
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!ctx) return
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
// Smooth mouse
|
||||
mouse.x += (mouse.tx - mouse.x) * 0.1
|
||||
mouse.y += (mouse.ty - mouse.y) * 0.1
|
||||
|
||||
const { accent, text } = getThemeColors()
|
||||
const time = performance.now() / 1000
|
||||
|
||||
// Moving grid effect
|
||||
offset = (time * 20) % gridSize // Slower movement
|
||||
|
||||
ctx.lineWidth = 1
|
||||
|
||||
// Draw vertical lines
|
||||
for (let x = 0; x <= width; x += gridSize) {
|
||||
const dx = x - mouse.x
|
||||
const dist = Math.abs(dx)
|
||||
const maxDist = 400
|
||||
|
||||
let drawX = x
|
||||
let alpha = 0.03 // Very subtle base opacity
|
||||
|
||||
if (dist < maxDist) {
|
||||
const force = Math.cos((dist / maxDist) * Math.PI / 2)
|
||||
drawX += (dx > 0 ? 1 : -1) * force * 15
|
||||
alpha += force * 0.08 // Subtle highlight
|
||||
}
|
||||
|
||||
const [r, g, b] = x % (gridSize * 4) === 0 ? accent : text
|
||||
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(drawX, 0)
|
||||
ctx.lineTo(drawX, height)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Draw horizontal lines
|
||||
for (let y = offset; y <= height; y += gridSize) {
|
||||
const dy = y - mouse.y
|
||||
const dist = Math.abs(dy)
|
||||
const maxDist = 400
|
||||
|
||||
let drawY = y
|
||||
let alpha = 0.03
|
||||
|
||||
if (dist < maxDist) {
|
||||
const force = Math.cos((dist / maxDist) * Math.PI / 2)
|
||||
drawY += (dy > 0 ? 1 : -1) * force * 15
|
||||
alpha += force * 0.08
|
||||
}
|
||||
|
||||
const [r, g, b] = Math.abs(y - offset) % (gridSize * 4) < 1 ? accent : text
|
||||
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, drawY)
|
||||
ctx.lineTo(width, drawY)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
function loop() {
|
||||
if (!running) return
|
||||
draw()
|
||||
raf = requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
mouse.tx = e.clientX
|
||||
mouse.ty = e.clientY
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!canvas.value) return
|
||||
ctx = canvas.value.getContext('2d')
|
||||
resize()
|
||||
window.addEventListener('resize', resize)
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
raf = requestAnimationFrame(loop)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
running = false
|
||||
cancelAnimationFrame(raf)
|
||||
window.removeEventListener('resize', resize)
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bg-grid {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user