Eidos

海上生明月,天涯共此时

By: Mayne

Install Latest (v0.0.1)

海上生明月,天涯共此时

// index.jsx
import React, { useEffect, useRef } from 'react'

// themes: Moonrise over the Sea, Serenity of Night
// visualization: A minimalist depiction of a glowing moon rising to the golden ratio line, with dynamic multi-layered waves representing the vast sea under a night sky, symbolizing serene connection and longing across distances. The moon has a subtle halo, the sea reflects a deep blue hue with animated waves moving from left to right, and a few twinkling stars dot the sky. The moonlight scatters softly on the sea surface for a natural glow.

const MoonOverSea = () => {
  const canvasRef = useRef < HTMLCanvasElement > (null)
  const containerRef = useRef < HTMLDivElement > (null)

  useEffect(() => {
    const canvas = canvasRef.current
    const container = containerRef.current
    if (!canvas || !container) return

    const ctx = canvas.getContext('2d')
    if (!ctx) return

    const updateSize = () => {
      if (!container) return
      const { clientWidth, clientHeight } = container
      const width = clientWidth
      const height = clientHeight
      canvas.width = width
      canvas.height = height
      canvas.style.width = `${width}px`
      canvas.style.height = `${height}px`
    }

    updateSize()

    const resizeObserver = new ResizeObserver(updateSize)
    resizeObserver.observe(container)

    // Define stars with random positions and twinkling effect
    const stars = Array.from({ length: 15 }, () => ({
      x: Math.random() * canvas.width,
      y: Math.random() * (canvas.height / 2),
      radius: Math.random() * 1.5 + 0.5,
      opacity: Math.random() * 0.5 + 0.5,
      twinkleSpeed: Math.random() * 0.05 + 0.02
    }))

    let time = 0
    const animate = () => {
      // Lighter night sky to ensure sea waves remain visible
      ctx.fillStyle = '#B0AEA5'
      ctx.fillRect(0, 0, canvas.width, canvas.height)

      // Draw twinkling stars
      stars.forEach(star => {
        ctx.beginPath()
        ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2)
        // Twinkling effect by varying opacity
        star.opacity = 0.5 + Math.sin(time * star.twinkleSpeed) * 0.3
        ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`
        ctx.fill()
      })

      // Define horizon (environment dividing line) at the middle
      const horizonY = canvas.height / 2

      // Calculate moon position with rising effect near horizon
      const centerX = canvas.width * 0.618 // Golden ratio line on x-axis
      const finalMoonY = canvas.height * 0.382 // Golden ratio line on y-axis
      const startMoonY = horizonY // Start position at horizon
      const moonY = startMoonY - (startMoonY - finalMoonY) * (1 - Math.exp(-time * 0.08))
      const moonRadius = Math.min(canvas.width, canvas.height) / 10

      // Draw moon halo (subtle glow effect)
      ctx.beginPath()
      const gradient = ctx.createRadialGradient(centerX, moonY, moonRadius * 0.5, centerX, moonY, moonRadius * 1.5)
      gradient.addColorStop(0, 'rgba(255, 255, 200, 0.6)')
      gradient.addColorStop(1, 'rgba(255, 255, 200, 0)')
      ctx.fillStyle = gradient
      ctx.arc(centerX, moonY, moonRadius * 1.5, 0, Math.PI * 2)
      ctx.fill()

      // Draw the moon itself
      ctx.beginPath()
      ctx.arc(centerX, moonY, moonRadius, 0, Math.PI * 2)
      ctx.fillStyle = 'rgba(255, 255, 200, 0.9)'
      ctx.fill()

      // Draw multiple layers of waves to represent the sea with a deep blue color
      const waveLayers = 3 // Number of wave layers for depth
      const waveAmplitude = canvas.height / 25
      const waveFrequencyBase = 0.01
      const waveSpeedBase = 0.2
      const waveYStart = horizonY + canvas.height / 10

      for (let layer = 0; layer < waveLayers; layer++) {
        const waveY = waveYStart + layer * (canvas.height / 20)
        const waveFrequency = waveFrequencyBase * (1 + layer * 0.2)
        const waveSpeed = waveSpeedBase * (1 - layer * 0.1)
        const opacity = 0.7 - layer * 0.1 // Decrease opacity for distant waves

        ctx.strokeStyle = `rgba(25, 45, 65, ${opacity})`
        ctx.lineWidth = 2 + layer * 0.5 // Slightly thicker lines for closer waves

        ctx.beginPath()
        ctx.moveTo(0, waveY)
        for (let x = 0; x < canvas.width; x += 5) {
          // Adjusted wave offset to create left-to-right movement by adding time * waveSpeed to the phase
          const waveOffset = Math.sin((x * waveFrequency) + (time * waveSpeed) + layer) * waveAmplitude
          ctx.lineTo(x, waveY + waveOffset)
        }
        ctx.stroke()

        // Fill the sea area below each wave with a gradient
        ctx.beginPath()
        ctx.moveTo(0, waveY)
        for (let x = 0; x < canvas.width; x += 5) {
          const waveOffset = Math.sin((x * waveFrequency) + (time * waveSpeed) + layer) * waveAmplitude
          ctx.lineTo(x, waveY + waveOffset)
        }
        ctx.lineTo(canvas.width, canvas.height)
        ctx.lineTo(0, canvas.height)
        ctx.closePath()
        const seaGradient = ctx.createLinearGradient(0, waveY, 0, canvas.height)
        seaGradient.addColorStop(0, `rgba(25, 45, 65, ${opacity - 0.1})`)
        seaGradient.addColorStop(1, `rgba(40, 60, 80, ${opacity - 0.2})`)
        ctx.fillStyle = seaGradient
        ctx.fill()
      }

      // Draw natural moonlight reflection on the sea with a scattered, soft effect
      const reflectionOpacity = 0.15 * (1 - (moonY - finalMoonY) / (startMoonY - finalMoonY))
      for (let i = 0; i < 5; i++) {
        ctx.beginPath()
        const offsetX = (Math.sin(time * 0.1 + i) * 20) // Slight horizontal shimmer
        ctx.moveTo(centerX + offsetX - moonRadius * (0.5 + i * 0.1), horizonY)
        ctx.bezierCurveTo(
          centerX + offsetX - moonRadius * (1 + i * 0.2), horizonY + canvas.height * 0.2,
          centerX + offsetX + moonRadius * (1 + i * 0.2), horizonY + canvas.height * 0.3,
          centerX + offsetX + moonRadius * (0.5 + i * 0.1), canvas.height
        )
        ctx.bezierCurveTo(
          centerX + offsetX + moonRadius * (0.5 + i * 0.1), horizonY + canvas.height * 0.3,
          centerX + offsetX - moonRadius * (0.5 + i * 0.1), horizonY + canvas.height * 0.2,
          centerX + offsetX - moonRadius * (0.5 + i * 0.1), horizonY
        )
        ctx.closePath()
        ctx.fillStyle = `rgba(255, 255, 200, ${reflectionOpacity * (0.8 - i * 0.15)})`
        ctx.fill()
      }

      time += 0.01
      requestAnimationFrame(animate)
    }
    animate()

    return () => resizeObserver.disconnect()
  }, [])

  return (
    <div
      ref={containerRef}
      style={{
        backgroundColor: '#B0AEA5',
        width: '100%',
        height: '100%',
        minHeight: '200px',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center'
      }}
    >
      <canvas ref={canvasRef} />
    </div>
  )
}

export default MoonOverSea

Information

Author
Mayne
Type
m_block
Latest Version
0.0.1
Last Updated
05/28/2025
Published
05/28/2025

Version History

  • v0.0.1 05/28/2025