
<!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/archives/46/
本站文章除注明[转载|引用|原文]出处外,均为本站原生内容,转载前请注明出处
这块还可以优化做的更好