昨天把 TrackballControls 加上去,畫面能拖曳旋轉、縮放,但還留了一個問題:拉動瀏覽器視窗大小時,瀏覽器上會出現捲軸,公園模型會隨著視窗變小,而跑位置,需要透過捲軸才能看到模型,並不是很方便,如圖
瀏覽器在視窗大小改變時,會發出一個叫 resize 的通知。
我們可以利用這個通知,來執行想要的事情,在 JS 裡面是 「聽事件」
window.addEventListener('resize', () => {
// 視窗改變時要做的事
});
依據:MDN 的 Window: resize event 文件說明,這個事件會在文件視圖(document view)被調整大小時觸發。
視窗變了,要更新三件事
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.handleResize();
});
調整完後可以看到,視窗大小被調整後,也不會如前面出現捲軸,導致模型畫面被切掉;而是模型大小因此跟著調整,不會需要移動捲軸才能看到模型

接在昨天的版本後面,標明新加的部分
// ★ 標記的是今天新加的,其他是昨天已有的:
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 1));
const sun = new THREE.DirectionalLight(0xffffff, 1);
sun.position.set(5, 10, 5);
scene.add(sun);
const controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 3.0;
controls.zoomSpeed = 2.5;
controls.panSpeed = 1.0;
controls.staticMoving = true;
const loader = new GLTFLoader();
loader.load(
'blender_output.glb',
(gltf) => {
scene.add(gltf.scene);
const box = new THREE.Box3().setFromObject(gltf.scene);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
camera.position.set(center.x, center.y + maxDim, center.z + maxDim);
camera.lookAt(center);
camera.far = maxDim * 10;
camera.updateProjectionMatrix();
controls.target.copy(center);
},
undefined,
(error) => console.error('載入失敗', error)
);
renderer.setAnimationLoop(() => {
controls.update();
renderer.render(scene, camera);
});
// ★ 今天新加的部分:視窗大小改變時,讓相機和畫布跟著更新
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight; // ★ 1. 重算長寬比
camera.updateProjectionMatrix(); // ★ 並讓相機套用這個改變
renderer.setSize(window.innerWidth, window.innerHeight); // ★ 2. 畫布跟著撐滿或縮小
controls.handleResize(); // ★ 3. TrackballControls 需要另外呼叫這行
});
為什麼放在檔案最後面: 這四行不依賴模型有沒有載入完成,camera、renderer、controls 一開始就建立好了,所以放在檔案最外層,不用放進 loader.load 的 callback(跟 Day14 的原則一致:只有依賴 gltf 或 center 的動作才要放進去)。
補充:如果 resize 觸發太頻繁
MDN 的 resize 事件文件提醒:如果 resize 事件觸發得太頻繁,需要控制事件觸發後實際執行的時間間隔(提到 debounce/節流的處理方式)。這次的運算量不大,先不處理,只記下來,等 Raycasting 判定範圍也要跟著更新時再回頭考慮。
視窗縮放處理好了,接下來要進入找威利遊戲最核心的技術:Raycasting,判斷滑鼠點擊的位置有沒有剛好點中藏起來的彩蛋。