By: Mayne
海上生明月,天涯共此时
// 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