ParticlesCanvas.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. <template>
  2. <canvas ref="canvasRef" class="particles-canvas" />
  3. </template>
  4. <script setup>
  5. const props = defineProps({
  6. size: {
  7. type: Number,
  8. default: 1.5
  9. }
  10. })
  11. import { onMounted, onUnmounted, ref } from 'vue'
  12. const canvasRef = ref(null)
  13. let animationId = null
  14. let particles = []
  15. const initParticles = (width, height) => {
  16. particles = []
  17. const particleCount = 30
  18. const centerX = width / 2
  19. const centerY = height / 2
  20. const maxDistance = Math.min(width, height * 2) * 0.4
  21. for (let i = 0; i < particleCount; i++) {
  22. const angle = Math.random() * Math.PI * 2
  23. const distance = maxDistance + Math.random() * 50
  24. particles.push({
  25. x: centerX + Math.cos(angle) * distance,
  26. y: centerY + Math.sin(angle) * distance,
  27. targetX: centerX,
  28. targetY: centerY,
  29. speed: 1.2 + Math.random() * 1.5,
  30. size: props.size + Math.random() * 1,
  31. opacity: 0.3 + Math.random() * 0.7
  32. })
  33. }
  34. }
  35. const drawAnimation = (ctx, width, height) => {
  36. const centerX = width / 2
  37. const centerY = height / 2
  38. ctx.clearRect(0, 0, width, height)
  39. if (particles.length > 0) {
  40. particles.forEach(particle => {
  41. const dx = particle.targetX - particle.x
  42. const dy = particle.targetY - particle.y
  43. const distance = Math.sqrt(dx * dx + dy * dy)
  44. if (distance < 5) {
  45. const angle = Math.random() * Math.PI * 2
  46. const maxDistance = Math.min(width, height * 2) * 0.4
  47. const newDistance = maxDistance + Math.random() * 50
  48. particle.x = centerX + Math.cos(angle) * newDistance
  49. particle.y = centerY + Math.sin(angle) * newDistance
  50. particle.opacity = 0.3 + Math.random() * 0.7
  51. } else {
  52. const moveX = (dx / distance) * particle.speed
  53. const moveY = (dy / distance) * particle.speed
  54. particle.x += moveX
  55. particle.y += moveY
  56. }
  57. ctx.globalAlpha = particle.opacity
  58. ctx.fillStyle = '#ffffff'
  59. ctx.beginPath()
  60. ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2)
  61. ctx.fill()
  62. ctx.globalAlpha = 1
  63. })
  64. }
  65. }
  66. const startAnimation = () => {
  67. stopAnimation()
  68. const canvas = canvasRef.value
  69. if (!canvas) return
  70. const rect = canvas.getBoundingClientRect()
  71. const width = rect.width || window.innerWidth
  72. const height = rect.height || rect.width / 2
  73. canvas.width = width
  74. canvas.height = height
  75. const ctx = canvas.getContext('2d')
  76. initParticles(width, height)
  77. const loop = () => {
  78. drawAnimation(ctx, width, height)
  79. animationId = requestAnimationFrame(loop)
  80. }
  81. loop()
  82. }
  83. const stopAnimation = () => {
  84. if (animationId) {
  85. cancelAnimationFrame(animationId)
  86. animationId = null
  87. }
  88. }
  89. onMounted(() => {
  90. startAnimation()
  91. window.addEventListener('resize', startAnimation)
  92. })
  93. onUnmounted(() => {
  94. stopAnimation()
  95. window.removeEventListener('resize', startAnimation)
  96. })
  97. </script>
  98. <style scoped>
  99. .particles-canvas {
  100. width: 100%;
  101. height: 100%;
  102. display: block;
  103. pointer-events: none;
  104. }
  105. </style>