|
|
@@ -0,0 +1,257 @@
|
|
|
+<template>
|
|
|
+ <canvas ref="waveCanvas" class="wave-canvas"></canvas>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: "WaveCanvas",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ canvas: null,
|
|
|
+ ctx: null,
|
|
|
+ animationId: null,
|
|
|
+ time: 0,
|
|
|
+ waves: [],
|
|
|
+ particles: []
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initCanvas();
|
|
|
+ this.initWaves();
|
|
|
+ this.initParticles();
|
|
|
+ this.animate();
|
|
|
+ window.addEventListener("resize", this.handleResize);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.animationId) {
|
|
|
+ cancelAnimationFrame(this.animationId);
|
|
|
+ }
|
|
|
+ window.removeEventListener("resize", this.handleResize);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ initCanvas() {
|
|
|
+ this.canvas = this.$refs.waveCanvas;
|
|
|
+ this.ctx = this.canvas.getContext("2d");
|
|
|
+ this.setCanvasSize();
|
|
|
+ },
|
|
|
+ setCanvasSize() {
|
|
|
+ const parent = this.canvas.parentElement;
|
|
|
+ this.canvas.width = parent.clientWidth;
|
|
|
+ this.canvas.height = parent.clientHeight;
|
|
|
+ },
|
|
|
+ handleResize() {
|
|
|
+ this.setCanvasSize();
|
|
|
+ this.initParticles();
|
|
|
+ },
|
|
|
+ initWaves() {
|
|
|
+ // 创建多层波浪参数
|
|
|
+ this.waves = [
|
|
|
+ {
|
|
|
+ amplitude: 30,
|
|
|
+ frequency: 0.01,
|
|
|
+ speed: 0.02,
|
|
|
+ yOffset: 0.7,
|
|
|
+ color: "rgba(64, 156, 255, 0.4)",
|
|
|
+ lineWidth: 2
|
|
|
+ },
|
|
|
+ {
|
|
|
+ amplitude: 25,
|
|
|
+ frequency: 0.015,
|
|
|
+ speed: 0.025,
|
|
|
+ yOffset: 0.75,
|
|
|
+ color: "rgba(100, 181, 246, 0.3)",
|
|
|
+ lineWidth: 1.5
|
|
|
+ },
|
|
|
+ {
|
|
|
+ amplitude: 20,
|
|
|
+ frequency: 0.008,
|
|
|
+ speed: 0.015,
|
|
|
+ yOffset: 0.8,
|
|
|
+ color: "rgba(144, 202, 249, 0.25)",
|
|
|
+ lineWidth: 1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ amplitude: 35,
|
|
|
+ frequency: 0.012,
|
|
|
+ speed: 0.018,
|
|
|
+ yOffset: 0.65,
|
|
|
+ color: "rgba(64, 156, 255, 0.2)",
|
|
|
+ lineWidth: 1.5
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ },
|
|
|
+ initParticles() {
|
|
|
+ this.particles = [];
|
|
|
+ const width = this.canvas.width;
|
|
|
+ const height = this.canvas.height;
|
|
|
+
|
|
|
+ // 创建点阵粒子
|
|
|
+ const cols = Math.floor(width / 15);
|
|
|
+ const rows = Math.floor(height / 15);
|
|
|
+
|
|
|
+ for (let i = 0; i < cols; i++) {
|
|
|
+ for (let j = 0; j < rows; j++) {
|
|
|
+ if (Math.random() > 0.6) {
|
|
|
+ this.particles.push({
|
|
|
+ x: i * 15 + Math.random() * 10,
|
|
|
+ y: j * 15 + Math.random() * 10,
|
|
|
+ baseY: j * 15,
|
|
|
+ size: Math.random() * 1.5 + 0.5,
|
|
|
+ opacity: Math.random() * 0.5 + 0.2,
|
|
|
+ speed: Math.random() * 0.5 + 0.2,
|
|
|
+ phase: Math.random() * Math.PI * 2
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ drawWave(wave, index) {
|
|
|
+ const ctx = this.ctx;
|
|
|
+ const width = this.canvas.width;
|
|
|
+ const height = this.canvas.height;
|
|
|
+ const baseY = height * wave.yOffset;
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.strokeStyle = wave.color;
|
|
|
+ ctx.lineWidth = wave.lineWidth;
|
|
|
+
|
|
|
+ // 绘制波浪线
|
|
|
+ for (let x = 0; x <= width; x += 2) {
|
|
|
+ const y = baseY +
|
|
|
+ Math.sin(x * wave.frequency + this.time * wave.speed + index) * wave.amplitude +
|
|
|
+ Math.sin(x * wave.frequency * 2 + this.time * wave.speed * 1.5) * (wave.amplitude * 0.5);
|
|
|
+
|
|
|
+ if (x === 0) {
|
|
|
+ ctx.moveTo(x, y);
|
|
|
+ } else {
|
|
|
+ ctx.lineTo(x, y);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.stroke();
|
|
|
+
|
|
|
+ // 填充波浪下方渐变
|
|
|
+ ctx.lineTo(width, height);
|
|
|
+ ctx.lineTo(0, height);
|
|
|
+ ctx.closePath();
|
|
|
+
|
|
|
+ const gradient = ctx.createLinearGradient(0, baseY - wave.amplitude, 0, height);
|
|
|
+ gradient.addColorStop(0, wave.color.replace(/[\d.]+\)$/, "0.1)"));
|
|
|
+ gradient.addColorStop(0.5, wave.color.replace(/[\d.]+\)$/, "0.05)"));
|
|
|
+ gradient.addColorStop(1, "transparent");
|
|
|
+
|
|
|
+ ctx.fillStyle = gradient;
|
|
|
+ ctx.fill();
|
|
|
+ },
|
|
|
+ drawParticles() {
|
|
|
+ const ctx = this.ctx;
|
|
|
+ const width = this.canvas.width;
|
|
|
+ const height = this.canvas.height;
|
|
|
+
|
|
|
+ this.particles.forEach(particle => {
|
|
|
+ // 粒子随波浪波动
|
|
|
+ const waveY = Math.sin(particle.x * 0.01 + this.time * 0.02) * 20 +
|
|
|
+ Math.sin(particle.x * 0.02 + this.time * 0.03) * 10;
|
|
|
+
|
|
|
+ const y = particle.baseY + waveY * particle.speed;
|
|
|
+
|
|
|
+ // 只在底部区域显示粒子
|
|
|
+ if (y > height * 0.5) {
|
|
|
+ const opacity = particle.opacity * (1 - (y - height * 0.5) / (height * 0.5));
|
|
|
+
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(particle.x, y, particle.size, 0, Math.PI * 2);
|
|
|
+ ctx.fillStyle = `rgba(100, 200, 255, ${Math.max(0, opacity)})`;
|
|
|
+ ctx.fill();
|
|
|
+
|
|
|
+ // 添加发光效果
|
|
|
+ if (opacity > 0.3) {
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(particle.x, y, particle.size * 3, 0, Math.PI * 2);
|
|
|
+ ctx.fillStyle = `rgba(100, 200, 255, ${opacity * 0.2})`;
|
|
|
+ ctx.fill();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ drawGrid() {
|
|
|
+ const ctx = this.ctx;
|
|
|
+ const width = this.canvas.width;
|
|
|
+ const height = this.canvas.height;
|
|
|
+
|
|
|
+ // 绘制淡淡的网格线
|
|
|
+ ctx.strokeStyle = "rgba(64, 156, 255, 0.03)";
|
|
|
+ ctx.lineWidth = 0.5;
|
|
|
+
|
|
|
+ const gridSize = 30;
|
|
|
+
|
|
|
+ for (let x = 0; x <= width; x += gridSize) {
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(x, height * 0.5);
|
|
|
+ ctx.lineTo(x, height);
|
|
|
+ ctx.stroke();
|
|
|
+ }
|
|
|
+
|
|
|
+ for (let y = Math.floor(height * 0.5); y <= height; y += gridSize) {
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(0, y);
|
|
|
+ ctx.lineTo(width, y);
|
|
|
+ ctx.stroke();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ drawGlow() {
|
|
|
+ const ctx = this.ctx;
|
|
|
+ const width = this.canvas.width;
|
|
|
+ const height = this.canvas.height;
|
|
|
+
|
|
|
+ // 底部发光效果
|
|
|
+ const gradient = ctx.createLinearGradient(0, height - 150, 0, height);
|
|
|
+ gradient.addColorStop(0, "transparent");
|
|
|
+ gradient.addColorStop(0.5, "rgba(64, 156, 255, 0.1)");
|
|
|
+ gradient.addColorStop(1, "rgba(64, 156, 255, 0.25)");
|
|
|
+
|
|
|
+ ctx.fillStyle = gradient;
|
|
|
+ ctx.fillRect(0, height - 150, width, 150);
|
|
|
+ },
|
|
|
+ animate() {
|
|
|
+ const ctx = this.ctx;
|
|
|
+ const width = this.canvas.width;
|
|
|
+ const height = this.canvas.height;
|
|
|
+
|
|
|
+ // 清空画布
|
|
|
+ ctx.clearRect(0, 0, width, height);
|
|
|
+
|
|
|
+ // 绘制网格
|
|
|
+ this.drawGrid();
|
|
|
+
|
|
|
+ // 绘制波浪
|
|
|
+ this.waves.forEach((wave, index) => {
|
|
|
+ this.drawWave(wave, index);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 绘制粒子
|
|
|
+ this.drawParticles();
|
|
|
+
|
|
|
+ // 绘制发光效果
|
|
|
+ this.drawGlow();
|
|
|
+
|
|
|
+ // 更新时间
|
|
|
+ this.time += 1;
|
|
|
+
|
|
|
+ // 继续动画
|
|
|
+ this.animationId = requestAnimationFrame(this.animate.bind(this));
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.wave-canvas {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+</style>
|