
<!DOCTYPE html> <html> <head> <title>三爱心水平对齐</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; cursor: pointer; } </style> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script> <script> // 初始化场景 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera.position.z = 10; // 创建爱心粒子系统 function createHeartParticles(size, color, scale = 1) { const particles = new THREE.BufferGeometry(); const initialPositions = []; const targetPositions = []; const colors = []; const delays = []; const heartShape = (t) => { const x = 16 * Math.pow(Math.sin(t), 3); const y = 13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t); return [x, y, 0]; }; const particleCount = 30000; // 适当减少单个爱心粒子数量以保证性能 for(let i = 0; i < particleCount; i++) { const t = THREE.MathUtils.randFloat(0, Math.PI * 2); const r = Math.pow(THREE.MathUtils.randFloat(0, 1), 0.7); const pos = heartShape(t); const scaledPos = [ pos[0] * r * size * scale, pos[1] * r * size * scale, (Math.random() - 0.5) * size * scale * 0.5 ]; targetPositions.push(...scaledPos); initialPositions.push( THREE.MathUtils.randFloatSpread(20), THREE.MathUtils.randFloatSpread(20), THREE.MathUtils.randFloatSpread(20) ); const baseColor = new THREE.Color(color); const randomFactor = color.r > 0.9 ? 0.8 + Math.random() * 0.2 : 0.7 + Math.random() * 0.3; baseColor.multiplyScalar(randomFactor); colors.push(baseColor.r, baseColor.g, baseColor.b); delays.push(Math.random() * 2); } particles.setAttribute('position', new THREE.Float32BufferAttribute(initialPositions, 3)); particles.setAttribute('targetPosition', new THREE.Float32BufferAttribute(targetPositions, 3)); particles.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)); particles.setAttribute('delay', new THREE.Float32BufferAttribute(delays, 1)); const material = new THREE.PointsMaterial({ size: size * 0.04, vertexColors: true, transparent: true, opacity: 0.9, sizeAttenuation: true }); return new THREE.Points(particles, material); } // 创建三个爱心(两个红色水平对齐,一个粉色居中) const heartLeft = createHeartParticles(0.08, new THREE.Color(1, 0.1, 0.1), 1); // 左侧红心 const heartRight = createHeartParticles(0.08, new THREE.Color(1, 0.1, 0.1), 1); // 右侧红心 const heartCenter = createHeartParticles(0.08, new THREE.Color(1, 0.6, 0.8), 0.6); // 中间粉心 // 设置位置:两个红心水平对齐,粉心稍高 heartLeft.position.set(-4.5, -0.5, 0); // 左侧位置 heartRight.position.set(4.5, -0.5, 0); // 右侧位置 heartCenter.position.set(0, -0.5, 0); // 中间偏上位置 scene.add(heartLeft, heartRight, heartCenter); // 添加灯光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); const pointLight = new THREE.PointLight(0xffffff, 0.8); pointLight.position.set(5, 5, 10); scene.add(ambientLight, pointLight); // 动画参数 const animation = { formationProgress: 0, isForming: true, pulseSpeed: 0.08, pulseIntensity: 0.15 }; // 鼠标点击交互 const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); window.addEventListener('click', (event) => { if (!animation.isForming) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects([heartLeft, heartRight, heartCenter]); if (intersects.length > 0) { const heart = intersects[0].object; heart.userData.isPulsing = true; heart.userData.pulse = 0; } } }); // 更新形成动画 function updateFormationAnimation(mesh, deltaTime) { const positions = mesh.geometry.attributes.position; const targetPositions = mesh.geometry.attributes.targetPosition; const delays = mesh.geometry.attributes.delay; const array = positions.array; for (let i = 0; i < array.length; i += 3) { const delay = delays.array[i/3]; const progress = Math.min(Math.max(animation.formationProgress - delay, 0) / (1 - delay), 1); const easedProgress = Math.pow(progress, 0.5); array[i] += (targetPositions.array[i] - array[i]) * easedProgress * 0.2; array[i+1] += (targetPositions.array[i+1] - array[i+1]) * easedProgress * 0.2; array[i+2] += (targetPositions.array[i+2] - array[i+2]) * easedProgress * 0.2; } positions.needsUpdate = true; } // 动画循环 let clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const deltaTime = clock.getDelta(); if (animation.isForming) { animation.formationProgress += deltaTime * 0.3; if (animation.formationProgress >= 2) { animation.isForming = false; } updateFormationAnimation(heartLeft, deltaTime); updateFormationAnimation(heartRight, deltaTime); updateFormationAnimation(heartCenter, deltaTime); } // 为所有爱心添加心跳动画 [heartLeft, heartRight, heartCenter].forEach(heart => { if (heart.userData.isPulsing) { heart.userData.pulse += deltaTime * animation.pulseSpeed; if (heart.userData.pulse >= Math.PI * 2) { heart.userData.isPulsing = false; } const scale = 1 + Math.sin(heart.userData.pulse) * animation.pulseIntensity; heart.scale.set(scale, scale, scale); } }); // 添加自动旋转动画 const time = Date.now() * 0.001; heartLeft.rotation.y = time * 0.3; heartRight.rotation.y = -time * 0.3; heartCenter.position.y = 1.2 + Math.sin(time) * 0.3; renderer.render(scene, camera); } // 窗口大小调整 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); animate(); </script> </body> </html>
本文最后更新时间 2025-05-12
文章链接地址:https://xzlo.blog/index.php/2025/05/12/3.html
本站文章除注明[转载|引用|原文]出处外,均为本站原生内容,转载前请注明出处