Three.js实现3D粒子爱心动画
· Html5 · 暂无标签
1. 在创建粒子时,为每个粒子存储初始位置(随机)和目标位置(爱心形状的位置)。2. 在动画循环中,根据时间或进度参数,插值每个粒子的位置从初始到目标。3. 添加一个控制形成过程的变量,比如formationProgress,从0到1,表示形成的进度。4. 在animate函数中,更新formationProgress,并计算每个粒子的当前位置。
image.png
<!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
本站文章除注明[转载|引用|原文]出处外,均为本站原生内容,转载前请注明出处


留言