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

Three.js PlaneGeometry适配窗口:保持宽高比并置于左上角

解决方案

1. 确定宽高比并计算平面尺寸

先定义旗帜的原始宽高比(根据你的实际需求调整,示例为2:1),以窗口高度为基准计算平面宽度:

// 自定义旗帜宽高比(宽度/高度)
const aspectRatio = 2;
// 获取当前窗口高度
const windowHeight = window.innerHeight;
// 计算平面尺寸:高度适配窗口高度,宽度按比例推导
const planeHeight = windowHeight;
const planeWidth = planeHeight * aspectRatio;

// 创建适配后的PlaneGeometry
const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight);

2. 将平面定位到左上角

Three.js默认场景原点在中心,使用正交相机更适合固定UI元素的位置计算,视口直接覆盖整个窗口:

// 创建正交相机,视口范围与窗口一致
const camera = new THREE.OrthographicCamera(
  -window.innerWidth / 2,
  window.innerWidth / 2,
  window.innerHeight / 2,
  -window.innerHeight / 2,
  0.1,
  1000
);

// 调整平面位置:左上角对应相机视口的(-窗口宽/2 + 平面宽/2, 窗口高/2 - 平面高/2)
plane.position.set(
  -window.innerWidth / 2 + planeWidth / 2,
  window.innerHeight / 2 - planeHeight / 2,
  0
);

如果使用透视相机,需要结合相机FOV和距离计算位置,但正交相机的逻辑更直观,适合这类固定UI需求。

3. 监听窗口resize事件,动态更新适配效果

窗口尺寸变化时,重新计算平面尺寸、相机参数和位置:

window.addEventListener('resize', () => {
  const newWindowHeight = window.innerHeight;
  const newPlaneHeight = newWindowHeight;
  const newPlaneWidth = newPlaneHeight * aspectRatio;

  // 更新平面几何体缩放比例
  geometry.scale(newPlaneWidth / planeWidth, newPlaneHeight / planeHeight, 1);
  planeWidth = newPlaneWidth;
  planeHeight = newPlaneHeight;

  // 更新正交相机视口参数
  camera.left = -window.innerWidth / 2;
  camera.right = window.innerWidth / 2;
  camera.top = window.innerHeight / 2;
  camera.bottom = -window.innerHeight / 2;
  camera.updateProjectionMatrix();

  // 更新平面位置
  plane.position.set(
    -window.innerWidth / 2 + newPlaneWidth / 2,
    window.innerHeight / 2 - newPlaneHeight / 2,
    0
  );

  // 更新渲染器尺寸
  renderer.setSize(window.innerWidth, window.innerHeight);
});

4. 整合到Angular组件(完整示例)

在Angular生命周期钩子中初始化Three.js逻辑,同时注意组件销毁时清理监听和资源:

import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import * as THREE from 'three';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit, OnDestroy {
  private scene!: THREE.Scene;
  private camera!: THREE.OrthographicCamera;
  private renderer!: THREE.WebGLRenderer;
  private plane!: THREE.Mesh;
  private aspectRatio = 2; // 自定义旗帜宽高比
  private planeWidth!: number;
  private planeHeight!: number;
  private resizeListener!: () => void;

  ngAfterViewInit(): void {
    this.initThreeJS();
    this.animate();
  }

  ngOnDestroy(): void {
    window.removeEventListener('resize', this.resizeListener);
    this.renderer.dispose();
  }

  private initThreeJS(): void {
    // 初始化场景
    this.scene = new THREE.Scene();

    // 初始化相机
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    this.camera = new THREE.OrthographicCamera(
      -windowWidth / 2,
      windowWidth / 2,
      windowHeight / 2,
      -windowHeight / 2,
      0.1,
      1000
    );
    this.camera.position.z = 5;

    // 计算平面尺寸
    this.planeHeight = windowHeight;
    this.planeWidth = this.planeHeight * this.aspectRatio;

    // 创建平面(替换为你的飘动材质)
    const geometry = new THREE.PlaneGeometry(this.planeWidth, this.planeHeight);
    const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
    this.plane = new THREE.Mesh(geometry, material);

    // 定位平面到左上角
    this.plane.position.set(
      -windowWidth / 2 + this.planeWidth / 2,
      windowHeight / 2 - this.planeHeight / 2,
      0
    );
    this.scene.add(this.plane);

    // 初始化渲染器
    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setSize(windowWidth, windowHeight);
    document.body.appendChild(this.renderer.domElement);

    // 绑定resize监听
    this.resizeListener = () => this.onWindowResize();
    window.addEventListener('resize', this.resizeListener);
  }

  private onWindowResize(): void {
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;

    // 更新相机参数
    this.camera.left = -windowWidth / 2;
    this.camera.right = windowWidth / 2;
    this.camera.top = windowHeight / 2;
    this.camera.bottom = -windowHeight / 2;
    this.camera.updateProjectionMatrix();

    // 更新平面尺寸与位置
    const newPlaneHeight = windowHeight;
    const newPlaneWidth = newPlaneHeight * this.aspectRatio;
    this.plane.scale.set(newPlaneWidth / this.planeWidth, newPlaneHeight / this.planeHeight, 1);
    this.planeWidth = newPlaneWidth;
    this.planeHeight = newPlaneHeight;

    this.plane.position.set(
      -windowWidth / 2 + this.planeWidth / 2,
      windowHeight / 2 - this.planeHeight / 2,
      0
    );

    // 更新渲染器
    this.renderer.setSize(windowWidth, windowHeight);
  }

  private animate(): void {
    requestAnimationFrame(() => this.animate());
    // 在此添加旗帜飘动的动画逻辑(如修改顶点位置)
    this.renderer.render(this.scene, this.camera);
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 06:15:13