3D粒子爱心+流星

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>心跳爱心 + 流星雨</title>
  <style>
    body { margin: 0; overflow: hidden; background: #000; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  <script>
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
    camera.position.z = 150;

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // ------------------ 爱心粒子 ------------------
    const heartParticleCount = 10000;
    const heartScale = 3.2;
    const heartZMin = -10, heartZMax = 10;
    const heartPositions = new Float32Array(heartParticleCount * 3);
    const heartSizes = new Float32Array(heartParticleCount);
    let accepted = 0;
    while (accepted < heartParticleCount) {
      const t = Math.random() * Math.PI * 2;
      const s = Math.pow(Math.random(), 1/6);
      let x = 16 * Math.pow(Math.sin(t), 3);
      let y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
      x = x * s * heartScale;
      y = y * s * heartScale;
      const radial = Math.sqrt(x * x + y * y);
      if (radial < 10 && Math.random() < 0.8) continue;
      heartPositions[accepted * 3] = x;
      heartPositions[accepted * 3 + 1] = y;
      heartPositions[accepted * 3 + 2] = (Math.random() - 0.5) * (heartZMax - heartZMin);
      heartSizes[accepted] = 1.0;
      accepted++;
    }

    const heartGeometry = new THREE.BufferGeometry();
    heartGeometry.setAttribute('position', new THREE.BufferAttribute(heartPositions, 3));
    heartGeometry.setAttribute('size', new THREE.BufferAttribute(heartSizes, 1));
    const heartUniforms = {
      pointColor: { value: new THREE.Color(0xff69b4) },
      opacity: { value: 0.9 }
    };
    const vertexShader = `
      attribute float size;
      void main() {
        vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
        gl_PointSize = size * (300.0 / -mvPosition.z);
        gl_Position = projectionMatrix * mvPosition;
      }
    `;
    const fragmentShader = `
      uniform vec3 pointColor;
      uniform float opacity;
      void main() {
        vec2 coord = gl_PointCoord - vec2(0.5);
        if (length(coord) > 0.5) discard;
        gl_FragColor = vec4(pointColor, opacity);
      }
    `;
    const heartMaterial = new THREE.ShaderMaterial({
      uniforms: heartUniforms,
      vertexShader,
      fragmentShader,
      transparent: true,
      depthWrite: false
    });
    const heartPoints = new THREE.Points(heartGeometry, heartMaterial);
    scene.add(heartPoints);

    // ------------------ 掉落粒子 & 漩涡 ------------------
    const fallingParticles = [];
    const groundParticles = [];
    let spawnInterval = 100;
    let lastSpawnTime = 0;
    const gravity = 0.05;
    const fallThresholdY = -60;

    function spawnFallingParticle() {
      const index = Math.floor(Math.random() * heartParticleCount);
      const i3 = index * 3;
      const pos = new THREE.Vector3(
        heartPositions[i3],
        heartPositions[i3 + 1],
        heartPositions[i3 + 2]
      );
      pos.x += (Math.random() - 0.5) * 2;
      pos.y += (Math.random() - 0.5) * 2;
      pos.z += (Math.random() - 0.5) * 2;
      const vel = new THREE.Vector3(
        (Math.random() - 0.5) * 0.2,
        -(Math.random() * 0.5 + 0.5),
        (Math.random() - 0.5) * 0.2
      );
      fallingParticles.push({ position: pos, velocity: vel });
    }

    let fallingGeometry = new THREE.BufferGeometry();
    let fallingPoints;
    function initFallingPoints() {
      fallingGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(0), 3));
      const mat = new THREE.PointsMaterial({
        color: 0xff69b4,
        size: 0.8,
        transparent: true,
        opacity: 0.9,
        depthWrite: false
      });
      fallingPoints = new THREE.Points(fallingGeometry, mat);
      scene.add(fallingPoints);
    }
    initFallingPoints();

    let groundGeometry = new THREE.BufferGeometry();
    let groundPoints;
    function initGroundPoints() {
      groundGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(0), 3));
      const mat = new THREE.PointsMaterial({
        color: 0xff69b4,
        size: 0.8,
        transparent: true,
        opacity: 0.9,
        depthWrite: false
      });
      groundPoints = new THREE.Points(groundGeometry, mat);
      scene.add(groundPoints);
    }
    initGroundPoints();

    const vortexCenter = new THREE.Vector3(0, fallThresholdY, 0);
    const swirlSpeed = 0.5;

    // ------------------ 流星雨 ------------------
    const maxMeteors = 150;
    const meteors = [];

    function createMeteor() {
      const group = new THREE.Group();

      const crossMat = new THREE.MeshBasicMaterial({
        color: 0x99ccff,
        transparent: true,
        opacity: 0.9,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
        side: THREE.DoubleSide
      });
      const planeGeo = new THREE.PlaneGeometry(4, 0.5);
      const cross1 = new THREE.Mesh(planeGeo, crossMat);
      const cross2 = new THREE.Mesh(planeGeo, crossMat);
      cross2.rotation.z = Math.PI / 2;
      group.add(cross1);
      group.add(cross2);

      const tailGeo = new THREE.ConeGeometry(0.5, 8, 8, 1, true);
      const tailMat = new THREE.MeshBasicMaterial({
        color: 0x00ccff,
        transparent: true,
        opacity: 0.6,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
        side: THREE.DoubleSide
      });
      const tail = new THREE.Mesh(tailGeo, tailMat);
      tail.position.y = -4;
      tail.rotation.x = Math.PI;
      group.add(tail);

      group.position.set(
        Math.random() * 400 - 200,
        200 + Math.random() * 100,
        Math.random() * 400 - 200
      );
      const velocity = new THREE.Vector3(-1.5, -3, 0).multiplyScalar(0.8 + Math.random() * 0.4);
      scene.add(group);
      meteors.push({ mesh: group, velocity });
    }

    // ------------------ 动画循环 ------------------
    const clock = new THREE.Clock();
    function animate() {
      requestAnimationFrame(animate);
      const delta = clock.getDelta();
      const elapsed = clock.elapsedTime * 1000;

      const beatFactor = 1 + 0.05 * Math.sin(clock.elapsedTime * Math.PI * 2);
      heartPoints.scale.set(beatFactor, beatFactor, beatFactor);

      if (elapsed - lastSpawnTime > spawnInterval) {
        spawnFallingParticle();
        lastSpawnTime = elapsed;
      }

      for (let i = fallingParticles.length - 1; i >= 0; i--) {
        const p = fallingParticles[i];
        p.velocity.y -= gravity;
        p.position.addScaledVector(p.velocity, delta * 60);
        if (p.position.y < fallThresholdY) {
          const dx = p.position.x - vortexCenter.x;
          const dz = p.position.z - vortexCenter.z;
          const radius = Math.sqrt(dx*dx + dz*dz);
          const angle = Math.atan2(dz, dx);
          groundParticles.push({
            position: new THREE.Vector3(p.position.x, fallThresholdY, p.position.z),
            radius, angle
          });
          fallingParticles.splice(i, 1);
        }
      }

      const fallingPositions = new Float32Array(fallingParticles.length * 3);
      fallingParticles.forEach((p, idx) => {
        fallingPositions[idx * 3]     = p.position.x;
        fallingPositions[idx * 3 + 1] = p.position.y;
        fallingPositions[idx * 3 + 2] = p.position.z;
      });
      fallingGeometry.setAttribute('position', new THREE.BufferAttribute(fallingPositions, 3));
      fallingGeometry.attributes.position.needsUpdate = true;
      fallingGeometry.setDrawRange(0, fallingParticles.length);

      if (groundParticles.length > 50) {
        groundParticles.forEach(p => {
          p.angle += swirlSpeed * delta;
          p.position.x = vortexCenter.x + p.radius * Math.cos(p.angle);
          p.position.z = vortexCenter.z + p.radius * Math.sin(p.angle);
          p.position.y = fallThresholdY;
        });
      }

      const groundPositions = new Float32Array(groundParticles.length * 3);
      groundParticles.forEach((p, idx) => {
        groundPositions[idx * 3]     = p.position.x;
        groundPositions[idx * 3 + 1] = p.position.y;
        groundPositions[idx * 3 + 2] = p.position.z;
      });
      groundGeometry.setAttribute('position', new THREE.BufferAttribute(groundPositions, 3));
      groundGeometry.attributes.position.needsUpdate = true;
      groundGeometry.setDrawRange(0, groundParticles.length);

      // 更新流星雨
      if (elapsed - lastSpawnTime > spawnInterval / 2 && meteors.length < maxMeteors) {
        createMeteor();
      }
      for (let i = meteors.length - 1; i >= 0; i--) {
        const m = meteors[i];
        m.mesh.position.addScaledVector(m.velocity, delta * 60);
        m.mesh.rotation.z += delta * 2;
        if (m.mesh.position.y < -120 || m.mesh.position.x < -220) {
          scene.remove(m.mesh);
          meteors.splice(i, 1);
        }
      }

      renderer.render(scene, camera);
    }
    animate();

    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
  </script>
</body>
</html>

代码效果

以下是一个简单的3D粒子爱心代码HTML源码: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>3D粒子爱心效果</title> <style> body { margin: 0; padding: 0; overflow: hidden; background-color: #000; } canvas { display: block; } </style> </head> <body> <canvas id="canvas"></canvas> <script> var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var W = canvas.width = window.innerWidth; var H = canvas.height = window.innerHeight; var particleCount = 200; var particles = []; function Particle() { this.x = Math.random() * W; this.y = Math.random() * H; this.vx = Math.random() * 20 - 10; this.vy = Math.random() * 20 - 10; this.gravity = 0.3; this.alpha = Math.random(); this.color = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; } Particle.prototype.draw = function() { ctx.globalAlpha = this.alpha; ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, 5, 0, Math.PI * 2, false); ctx.fill(); }; function init() { for (var i = 0; i < particleCount; i++) { particles.push(new Particle()); } } function update() { ctx.clearRect(0, 0, W, H); for (var i = 0; i < particleCount; i++) { var p = particles[i]; p.vy += p.gravity; p.x += p.vx; p.y += p.vy; if (p.x > W || p.x < 0 || p.y > H || p.y < 0) { particles[i] = new Particle(); } p.draw(); } } init(); setInterval(update, 1000 / 60); </script> </body> </html> ``` 这个代码使用了HTML5的Canvas标签和JavaScript来实现3D粒子爱心效果。它通过在Canvas上绘制大量的带有随机颜色和透明度的小圆点来模拟出一个3D爱心效果。通过改变圆点的位置和速度,可以让它们在屏幕上自由运动,从而创造出动态的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值