You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

特定div未在指定位置渲染:Three.js鸟群动画布局异常求助

问题分析与解决方案

我一眼就揪出问题根源了——你的JavaScript代码里有个多余的DOM操作,直接把目标容器从原本的结构里“拽”出来塞到了body末尾,这就是#canvas-div跑到#home1后面的罪魁祸首!

核心问题代码:

container = document.getElementById("canvas-div"), document.body.appendChild(container)

你先通过getElementById获取了已经存在于.canvas-wrapper里的#canvas-div,紧接着又调用document.body.appendChild(container)——DOM元素不能同时存在于两个父节点下,这行代码会把#canvas-div从.canvas-wrapper里移除,再追加到body的最后面,自然就出现在#home1之后了。

完整修复步骤:

  1. 删除多余的appendChild操作:既然#canvas-div已经在HTML结构里了,完全不需要再把它加到body里。
  2. 修正CSS语法错误:你的#canvas-div CSS里background:width:100%;是无效语法,直接删掉;同时调整定位逻辑,确保容器固定在首屏顶部。
  3. 修复窗口resize的尺寸逻辑:原来的代码把渲染器尺寸设为窗口大小,会导致动画超出容器,改成适配容器尺寸。

修正后的完整代码

HTML:

<div class="canvas-wrapper">
  <div id="canvas-div"></div>
</div>
<div id="home1"></div>

CSS:

.canvas-wrapper {
  /* 用vh单位更直观控制首屏占比,这里设为10%高度 */
  height: 10vh;
  position: relative;
  /* 确保容器不会被其他元素挤走 */
  width: 100%;
}
#canvas-div {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  position: relative;
  top: 0;
  left: 0;
  background-color: grey;
  overflow: hidden;
}
#home1 {
  height: 300px;
  width: 100%;
}

JavaScript:

function init() {
  // 只获取容器,不要执行appendChild操作
  container = document.getElementById("canvas-div");
  camera = new THREE.PerspectiveCamera(75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000);
  camera.position.z = 450;
  scene = new THREE.Scene();
  birds = [];
  boids = [];

  for (var i = 0; i < 30; i++) {
    boid = boids[i] = new Boid();
    boid.position.x = 400 * Math.random() - 200;
    boid.position.y = 400 * Math.random() - 200;
    boid.position.z = 400 * Math.random() - 200;
    boid.velocity.x = 2 * Math.random() - 1;
    boid.velocity.y = 2 * Math.random() - 1;
    boid.velocity.z = 2 * Math.random() - 1;
    boid.setAvoidWalls(true);
    boid.setWorldSize(500, 500, 400);

    bird = birds[i] = new THREE.Mesh(
      new Bird(),
      new THREE.MeshBasicMaterial({
        color: 0xffffff * Math.random(),
        side: THREE.DoubleSide
      })
    );
    bird.phase = Math.floor(62.83 * Math.random());
    scene.add(bird);
  }

  renderer = new THREE.CanvasRenderer({ alpha: true });
  renderer.setClearColor(0xffffff, 0);
  // 用容器的实际尺寸设置渲染器大小
  renderer.setSize(container.offsetWidth, container.offsetHeight);
  container.appendChild(renderer.domElement);

  window.addEventListener("resize", onWindowResize, false);
}

function onWindowResize() {
  // 窗口变化时,根据容器尺寸更新相机和渲染器
  const containerWidth = container.offsetWidth;
  const containerHeight = container.offsetHeight;
  camera.aspect = containerWidth / containerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(containerWidth, containerHeight);
}

function onDocumentMouseMove(i) {
  var t = new THREE.Vector3(i.clientX - SCREEN_WIDTH_HALF, -i.clientY + SCREEN_HEIGHT_HALF, 0);
  for (var o = 0, e = boids.length; o < e; o++) {
    boid = boids[o];
    t.z = boid.position.z;
    boid.repulse(t);
  }
}

function animate() {
  requestAnimationFrame(animate);
  render();
}

function render() {
  for (var i = 0, t = birds.length; i < t; i++) {
    boid = boids[i];
    boid.run(boids);
    bird = birds[i];
    bird.position.copy(boids[i].position);
    color = bird.material.color;
    color.r = color.g = color.b = 0xcccccc;
    bird.rotation.y = Math.atan2(-boid.velocity.z, boid.velocity.x);
    bird.rotation.z = Math.asin(boid.velocity.y / boid.velocity.length());
    bird.phase = (bird.phase + (Math.max(0, bird.rotation.z) + 0.1)) % 62.83;
    bird.geometry.vertices[5].y = bird.geometry.vertices[4].y = 5 * Math.sin(bird.phase);
  }
  renderer.render(scene, camera);
}

var Boid = function() {
  var i, t, o = new THREE.Vector3(),
    e = 500,
    n = 500,
    s = 200,
    r = 100,
    a = 4,
    d = 0.1,
    c = false;

  this.position = new THREE.Vector3();
  this.velocity = new THREE.Vector3();
  i = new THREE.Vector3();

  this.setGoal = function(i) {
    t = i;
  };
  this.setAvoidWalls = function(i) {
    c = i;
  };
  this.setWorldSize = function(i, t, o) {
    e = i;
    n = t;
    s = o;
  };
  this.run = function(t) {
    if (c) {
      o.set(-e, this.position.y, this.position.z);
      o = this.avoid(o);
      o.multiplyScalar(5);
      i.add(o);

      o.set(e, this.position.y, this.position.z);
      o = this.avoid(o);
      o.multiplyScalar(5);
      i.add(o);

      o.set(this.position.x, -n, this.position.z);
      o = this.avoid(o);
      o.multiplyScalar(5);
      i.add(o);

      o.set(this.position.x, n, this.position.z);
      o = this.avoid(o);
      o.multiplyScalar(5);
      i.add(o);

      o.set(this.position.x, this.position.y, -s);
      o = this.avoid(o);
      o.multiplyScalar(5);
      i.add(o);

      o.set(this.position.x, this.position.y, s);
      o = this.avoid(o);
      o.multiplyScalar(5);
      i.add(o);
    }

    if (Math.random() > 0.5) this.flock(t);
    this.move();
  };
  this.flock = function(o) {
    if (t) i.add(this.reach(t, 0.005));
    i.add(this.alignment(o));
    i.add(this.cohesion(o));
    i.add(this.separation(o));
  };
  this.move = function() {
    this.velocity.add(i);
    var t = this.velocity.length();
    if (t > a) this.velocity.divideScalar(t / a);
    this.position.add(this.velocity);
    i.set(0, 0, 0);
  };
  this.checkBounds = function() {
    if (this.position.x > e) this.position.x = -e;
    if (this.position.x < -e) this.position.x = e;
    if (this.position.y > n) this.position.y = -n;
    if (this.position.y < -n) this.position.y = n;
    if (this.position.z > s) this.position.z = -s;
    if (this.position.z < -s) this.position.z = s;
  };
  this.avoid = function(i) {
    var t = new THREE.Vector3();
    t.copy(this.position);
    t.sub(i);
    t.multiplyScalar(1 / this.position.distanceToSquared(i));
    return t;
  };
  this.repulse = function(t) {
    var o = this.position.distanceTo(t);
    if (o < 150) {
      var e = new THREE.Vector3();
      e.subVectors(this.position, t);
      e.multiplyScalar(0.5 / o);
      i.add(e);
    }
  };
  this.reach = function(i, t) {
    var o = new THREE.Vector3();
    o.subVectors(i, this.position);
    o.multiplyScalar(t);
    return o;
  };
  this.alignment = function(i) {
    var t, o = new THREE.Vector3(),
      e = 0,
      n = 0,
      s = i.length;
    for (; n < s; n++) {
      if (Math.random() > 0.6) continue;
      t = i[n];
      distance = t.position.distanceTo(this.position);
      if (distance > 0 && distance <= r) {
        o.add(t.velocity);
        e++;
      }
    }
    if (e > 0) {
      o.divideScalar(e);
      var a = o.length();
      if (a > d) o.divideScalar(a / d);
    }
    return o;
  };
  this.cohesion = function(i) {
    var t, o, e = new THREE.Vector3(),
      n = new THREE.Vector3(),
      s = 0,
      a = 0,
      c = i.length;
    for (; a < c; a++) {
      if (Math.random() > 0.6) continue;
      t = i[a];
      o = t.position.distanceTo(this.position);
      if (o > 0 && o <= r) {
        e.add(t.position);
        s++;
      }
    }
    if (s > 0) {
      e.divideScalar(s);
      n.subVectors(e, this.position);
      var h = n.length();
      if (h > d) n.divideScalar(h / d);
    }
    return n;
  };
  this.separation = function(i) {
    var t, o, e = new THREE.Vector3(),
      n = new THREE.Vector3(),
      s = 0,
      a = i.length;
    for (; s < a; s++) {
      if (Math.random() > 0.6) continue;
      t = i[s];
      o = t.position.distanceTo(this.position);
      if (o > 0 && o <= r) {
        n.subVectors(this.position, t.position);
        n.normalize();
        n.divideScalar(o);
        e.add(n);
      }
    }
    return e;
  };
};

var SCREEN_WIDTH = window.innerWidth,
  SCREEN_HEIGHT = window.innerHeight,
  SCREEN_WIDTH_HALF = SCREEN_WIDTH / 2,
  SCREEN_HEIGHT_HALF = SCREEN_HEIGHT / 2,
  camera, scene, renderer, birds, bird, boid, boids, stats;

init();
animate();

额外优化说明:

  • 我把JS里紧凑的逗号表达式拆分成了换行写法,同时把!0/!1改成了更易读的true/false,30 > i调整为常规的i < 30。
  • 修正了CSS里的无效background语法,改为标准的background-color属性。
  • 窗口resize时现在会根据容器的实际尺寸更新相机比例和渲染器大小,确保动画始终适配容器范围。

这样修改后,#canvas-div就会老老实实待在.canvas-wrapper里,固定在首屏顶部,不会再被移到#home1后面了!

内容的提问来源于stack exchange,提问作者Phillip Dews

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:45:16