Files
ogame-vue-ts/src/components/ui/particles-bg/ParticlesBg.vue
谦君 724a70bebb docs: 新增西班牙语和日语README并优化多语言文档
新增README-ES.md(西班牙语)和README-JA.md(日语)文档,完善多语言README互链。优化各语言README徽章、技术栈、外链格式及语言切换区,提升文档一致性与可读性。
2025-12-25 18:25:08 +08:00

239 lines
6.8 KiB
Vue

<template>
<div ref="canvasContainerRef" :class="$props.class" aria-hidden="true">
<canvas ref="canvasRef" />
</div>
</template>
<script setup lang="ts">
import { useMouse, useDevicePixelRatio } from '@vueuse/core'
import { ref, onMounted, onBeforeUnmount, watch, computed, reactive } from 'vue'
type Circle = {
x: number
y: number
translateX: number
translateY: number
size: number
alpha: number
targetAlpha: number
dx: number
dy: number
magnetism: number
}
type Props = {
color?: string
quantity?: number
staticity?: number
ease?: number
class?: string
}
const props = withDefaults(defineProps<Props>(), {
color: '#FFF',
quantity: 100,
staticity: 50,
ease: 50,
class: ''
})
const canvasRef = ref<HTMLCanvasElement | null>(null)
const canvasContainerRef = ref<HTMLDivElement | null>(null)
const context = ref<CanvasRenderingContext2D | null>(null)
const circles = ref<Circle[]>([])
const mouse = reactive<{ x: number; y: number }>({ x: 0, y: 0 })
const canvasSize = reactive<{ w: number; h: number }>({ w: 0, h: 0 })
const { x: mouseX, y: mouseY } = useMouse()
const { pixelRatio } = useDevicePixelRatio()
const color = computed(() => {
// Remove the leading '#' if it's present
let hex = props.color.replace(/^#/, '')
// If the hex code is 3 characters, expand it to 6 characters
if (hex.length === 3) {
hex = hex
.split('')
.map(char => char + char)
.join('')
}
// Parse the r, g, b values from the hex string
const bigint = parseInt(hex, 16)
const r = (bigint >> 16) & 255 // Extract the red component
const g = (bigint >> 8) & 255 // Extract the green component
const b = bigint & 255 // Extract the blue component
// Return the RGB values as a string separated by spaces
return `${r} ${g} ${b}`
})
const resizeCanvas = () => {
if (canvasContainerRef.value && canvasRef.value && context.value) {
circles.value.length = 0
canvasSize.w = canvasContainerRef.value.offsetWidth
canvasSize.h = canvasContainerRef.value.offsetHeight
canvasRef.value.width = canvasSize.w * pixelRatio.value
canvasRef.value.height = canvasSize.h * pixelRatio.value
canvasRef.value.style.width = canvasSize.w + 'px'
canvasRef.value.style.height = canvasSize.h + 'px'
context.value.scale(pixelRatio.value, pixelRatio.value)
}
}
const circleParams = (): Circle => {
const x = Math.floor(Math.random() * canvasSize.w)
const y = Math.floor(Math.random() * canvasSize.h)
const translateX = 0
const translateY = 0
const size = Math.floor(Math.random() * 2) + 1
const alpha = 0
const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1))
const dx = (Math.random() - 0.5) * 0.2
const dy = (Math.random() - 0.5) * 0.2
const magnetism = 0.1 + Math.random() * 4
return {
x,
y,
translateX,
translateY,
size,
alpha,
targetAlpha,
dx,
dy,
magnetism
}
}
const drawCircle = (circle: Circle, update = false) => {
if (context.value) {
const { x, y, translateX, translateY, size, alpha } = circle
context.value.translate(translateX, translateY)
context.value.beginPath()
context.value.arc(x, y, size, 0, 2 * Math.PI)
context.value.fillStyle = `rgba(${color.value.split(' ').join(', ')}, ${alpha})`
context.value.fill()
context.value.setTransform(pixelRatio.value, 0, 0, pixelRatio.value, 0, 0)
if (!update) {
circles.value.push(circle)
}
}
}
const clearContext = () => {
if (context.value) {
context.value.clearRect(0, 0, canvasSize.w, canvasSize.h)
}
}
const drawParticles = () => {
clearContext()
const particleCount = props.quantity
for (let i = 0; i < particleCount; i++) {
const circle = circleParams()
drawCircle(circle)
}
}
const initCanvas = () => {
resizeCanvas()
drawParticles()
}
const onMouseMove = () => {
if (canvasRef.value) {
const rect = canvasRef.value.getBoundingClientRect()
const { w, h } = canvasSize
const x = mouseX.value - rect.left - w / 2
const y = mouseY.value - rect.top - h / 2
const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2
if (inside) {
mouse.x = x
mouse.y = y
}
}
}
const remapValue = (value: number, start1: number, end1: number, start2: number, end2: number): number => {
const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2
return remapped > 0 ? remapped : 0
}
const animate = () => {
clearContext()
circles.value.forEach((circle, i) => {
// Handle the alpha value
const edge = [
circle.x + circle.translateX - circle.size, // distance from left edge
canvasSize.w - circle.x - circle.translateX - circle.size, // distance from right edge
circle.y + circle.translateY - circle.size, // distance from top edge
canvasSize.h - circle.y - circle.translateY - circle.size // distance from bottom edge
]
const closestEdge = edge.reduce((a, b) => Math.min(a, b))
const remapClosestEdge = parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2))
if (remapClosestEdge > 1) {
circle.alpha += 0.02
if (circle.alpha > circle.targetAlpha) circle.alpha = circle.targetAlpha
} else {
circle.alpha = circle.targetAlpha * remapClosestEdge
}
circle.x += circle.dx
circle.y += circle.dy
circle.translateX += (mouse.x / (props.staticity / circle.magnetism) - circle.translateX) / props.ease
circle.translateY += (mouse.y / (props.staticity / circle.magnetism) - circle.translateY) / props.ease
// circle gets out of the canvas
if (
circle.x < -circle.size ||
circle.x > canvasSize.w + circle.size ||
circle.y < -circle.size ||
circle.y > canvasSize.h + circle.size
) {
// remove the circle from the array
circles.value.splice(i, 1)
// create a new circle
const newCircle = circleParams()
drawCircle(newCircle)
// update the circle position
} else {
drawCircle(
{
...circle,
x: circle.x,
y: circle.y,
translateX: circle.translateX,
translateY: circle.translateY,
alpha: circle.alpha
},
true
)
}
})
window.requestAnimationFrame(animate)
}
onMounted(() => {
if (canvasRef.value) {
context.value = canvasRef.value.getContext('2d')
}
initCanvas()
animate()
window.addEventListener('resize', initCanvas)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', initCanvas)
})
watch([mouseX, mouseY], () => {
onMouseMove()
})
</script>