mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
新增README-ES.md(西班牙语)和README-JA.md(日语)文档,完善多语言README互链。优化各语言README徽章、技术栈、外链格式及语言切换区,提升文档一致性与可读性。
239 lines
6.8 KiB
Vue
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>
|