效果
<template><div id="waves" />
</template><script setup>
import { ref, onMounted, onUnmounted } from "vue";
import * as THREE from "three";const amountX = ref(50);
const amountY = ref(50);
const color = ref("#e1b284");
const topOffset = ref(350);let count = 0;
let mouseX = 0;
let windowHalfX = null;
let camera = null;
let scene = null;
let particles = null;
let renderer = null;const init = () => {const SEPARATION = 100;const SCREEN_WIDTH = window.innerWidth;const SCREEN_HEIGHT = window.innerHeight;const container = document.createElement("div");windowHalfX = window.innerWidth / 2;container.style.position = "relative";container.style.top = `${topOffset.value}px`;container.style.height = `${SCREEN_HEIGHT - topOffset.value}px`;const waves = document.getElementById("waves");waves.appendChild(container);camera = new THREE.PerspectiveCamera(75,SCREEN_WIDTH / SCREEN_HEIGHT,1,10000,);camera.position.z = 1000;scene = new THREE.Scene();const numParticles = amountX.value * amountY.value;const positions = new Float32Array(numParticles * 3);const scales = new Float32Array(numParticles);let i = 0;let j = 0;for (let ix = 0; ix < amountX.value; ix++) {for (let iy = 0; iy < amountY.value; iy++) {positions[i] = ix * SEPARATION - (amountX.value * SEPARATION) / 2;positions[i + 1] = 0;positions[i + 2] = iy * SEPARATION - (amountY.value * SEPARATION) / 2;scales[j] = 1;i += 3;j++;}}const geometry = new THREE.BufferGeometry();geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));geometry.setAttribute("scale", new THREE.BufferAttribute(scales, 1));const material = new THREE.ShaderMaterial({uniforms: {color: { value: new THREE.Color(color.value) },},vertexShader: `attribute float scale;void main() {vec4 mvPosition = modelViewMatrix * vec4( position, 2.0 );gl_PointSize = scale * ( 300.0 / - mvPosition.z );gl_Position = projectionMatrix * mvPosition;}`,fragmentShader: `uniform vec3 color;void main() {if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;gl_FragColor = vec4( color, 1.0 );}`,});particles = new THREE.Points(geometry, material);scene.add(particles);renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });renderer.setSize(container.clientWidth, container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);renderer.setClearAlpha(0);container.appendChild(renderer.domElement);window.addEventListener("resize", onWindowResize, { passive: false });document.addEventListener("mousemove", onDocumentMouseMove, {passive: false,});document.addEventListener("touchstart", onDocumentTouchStart, {passive: false,});document.addEventListener("touchmove", onDocumentTouchMove, {passive: false,});
};const render = () => {camera.position.x += (mouseX - camera.position.x) * 0.05;camera.position.y = 400;camera.lookAt(scene.position);const positions = particles.geometry.attributes.position.array;const scales = particles.geometry.attributes.scale.array;let i = 0;let j = 0;for (let ix = 0; ix < amountX.value; ix++) {for (let iy = 0; iy < amountY.value; iy++) {positions[i + 1] =Math.sin((ix + count) * 0.3) * 100 + Math.sin((iy + count) * 0.5) * 100;scales[j] =(Math.sin((ix + count) * 0.3) + 1) * 8 +(Math.sin((iy + count) * 0.5) + 1) * 8;i += 3;j++;}}particles.geometry.attributes.position.needsUpdate = true;particles.geometry.attributes.scale.needsUpdate = true;renderer.render(scene, camera);count += 0.1;
};const animate = () => {requestAnimationFrame(animate);render();
};const onDocumentMouseMove = (event) => {mouseX = event.clientX - windowHalfX;
};const onDocumentTouchStart = (event) => {if (event.touches.length === 1) {mouseX = event.touches[0].pageX - windowHalfX;}
};const onDocumentTouchMove = (event) => {if (event.touches.length === 1) {event.preventDefault();mouseX = event.touches[0].pageX - windowHalfX;}
};const onWindowResize = () => {windowHalfX = window.innerWidth / 2;camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);
};onMounted(() => {init();animate();
});onUnmounted(() => {window.removeEventListener("resize", onWindowResize);document.removeEventListener("mousemove", onDocumentMouseMove);document.removeEventListener("touchstart", onDocumentTouchStart);document.removeEventListener("touchmove", onDocumentTouchMove);
});
</script>