iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0

前面幾天我們都是使用固定大小的 800 × 500 來顯示 Three.js 畫面。
從今天開始,我們要把原本的小型 Three.js 畫面改造成 全螢幕 3D 宇宙場景 同時把原本位於畫面上方的標題改成浮在 3D 場景上方。
今天完成之後,整個專案的外觀就會開始比較接近最後的成品。

一、今天完成目標

今天我們會完成:

  • Three.js 畫布改成全螢幕 移除原本固定的 800 × 500
  • Camera 自動配合瀏覽器比例
  • Renderer 自動配合瀏覽器大小
  • 視窗縮放時自動調整畫面
  • 標題浮在 3D 場景左上角
  • 建立正式的太陽

二、完整程式碼 (說明跳第三段)

index.html

<!DOCTYPE html>
<html lang="zh-TW">

<head>
    <meta charset="UTF-8">

    <meta
        name="viewport"
        content="width=device-width, initial-scale=1.0"
    >

    <title>從 Web 到宇宙</title>

    <link
        rel="stylesheet"
        href="css/style.css"
    >
</head>

<body>

    <div id="scene"></div>

    <div class="title">
        <h1>從 Web 到宇宙</h1>
        <p>DAY 9|Full Screen Solar System</p>
    </div>

    <script
        type="module"
        src="js/main.js"
    ></script>

</body>

</html>

css/style.css

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

body {
    background: #000000;
    color: white;
    font-family: Arial, sans-serif;
}

/* Three.js 場景 */

#scene {
    position: fixed;
    top: 0;
    left: 0;

    width: 100vw;
    height: 100vh;
}

#scene canvas {
    display: block;

    width: 100%;
    height: 100%;
}

/* 標題 */

.title {
    position: fixed;

    top: 30px;
    left: 40px;

    z-index: 10;

    pointer-events: none;
}

.title h1 {
    margin: 0;

    font-size: 36px;
    letter-spacing: 2px;
}

.title p {
    margin-top: 8px;

    color: #aaaaaa;
    font-size: 16px;
}

js/main.js

import * as THREE from
    "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js";


// ========================
// Scene
// ========================

const scene = new THREE.Scene();

scene.background =
    new THREE.Color(0x000000);


// ========================
// Camera
// ========================

const camera =
    new THREE.PerspectiveCamera(
        75,
        window.innerWidth /
        window.innerHeight,
        0.1,
        1000
    );

camera.position.z = 8;


// ========================
// Renderer
// ========================

const renderer =
    new THREE.WebGLRenderer({
        antialias: true
    });

renderer.setSize(
    window.innerWidth,
    window.innerHeight
);

renderer.setPixelRatio(
    Math.min(
        window.devicePixelRatio,
        2
    )
);

document
    .getElementById("scene")
    .appendChild(
        renderer.domElement
    );


// ========================
// Sun
// ========================

const sunGeometry =
    new THREE.SphereGeometry(
        2,
        64,
        64
    );

const sunMaterial =
    new THREE.MeshBasicMaterial({
        color: 0xffaa00
    });

const sun =
    new THREE.Mesh(
        sunGeometry,
        sunMaterial
    );

scene.add(sun);


// ========================
// Resize
// ========================

window.addEventListener(
    "resize",
    () => {

        camera.aspect =
            window.innerWidth /
            window.innerHeight;

        camera.updateProjectionMatrix();

        renderer.setSize(
            window.innerWidth,
            window.innerHeight
        );

        renderer.setPixelRatio(
            Math.min(
                window.devicePixelRatio,
                2
            )
        );
    }
);


// ========================
// Animation
// ========================

function animate() {

    requestAnimationFrame(
        animate
    );

    sun.rotation.y += 0.005;

    renderer.render(
        scene,
        camera
    );
}

animate();

三、程式碼簡單說明

  1. 移除原本的固定畫面

之前我們使用:

renderer.setSize(
    800,
    500
);

代表 Three.js 永遠只有 800 × 500

今天改成:

renderer.setSize(
    window.innerWidth,
    window.innerHeight
);

window.innerWidth 代表目前瀏覽器的寬度。
window.innerHeight 代表目前瀏覽器的高度。
所以現在 Three.js 就會直接填滿整個瀏覽器。

  1. Camera 也必須跟著改
const camera =
    new THREE.PerspectiveCamera(
        75,
        800 / 500,
        0.1,
        1000
    );

現在改成:

const camera =
    new THREE.PerspectiveCamera(
        75,
        window.innerWidth /
        window.innerHeight,
        0.1,
        1000
    );

第二個參數就是畫面寬高比,也就是瀏覽器寬度、瀏覽器高度
這樣 Camera 看到的比例就會與瀏覽器一致。

  1. 標題不再佔用網頁空間

HTML:

<div class="title">
    <h1>從 Web 到宇宙</h1>
    <p>DAY 9|Full Screen Solar System</p>
</div>

CSS:

.title {
    position: fixed;

    top: 30px;
    left: 40px;

    z-index: 10;
}

因為使用 position: fixed; 所以標題會直接浮在 Three.js 畫面上。
這個做法非常重要,因為之後開始製作 UI 時速度控制、播放、暫停、行星資訊
也會使用類似方式放在 Three.js 畫面上。

  1. 為什麼加入 z-index、z-index: 10

因為我們希望標題永遠顯示在 Canvas 上面。
不然 Three.js 的 Canvas 有可能把文字蓋住。

  1. pointer-events: none

我們還加入pointer-events: none;目前標題只是文字,不需要點擊。
所以讓滑鼠事件直接穿過標題這對後面的滑鼠旋轉、滑鼠縮放會比較方便。
真正需要按的 UI 按鈕,之後再把 pointer-events 打開。

  1. 視窗大小自動調整

這是今天非常重要的新功能:

window.addEventListener(
    "resize",
    () => {

        camera.aspect =
            window.innerWidth /
            window.innerHeight;

        camera.updateProjectionMatrix();

        renderer.setSize(
            window.innerWidth,
            window.innerHeight
        );
    }
);

當使用者改變瀏覽器大小:

瀏覽器變大
↓
Camera 更新
↓
Renderer 更新
↓
Three.js 填滿畫面

所以不管 1920 × 1080、1366 × 768、2560 × 1440 Three.js 都會自動調整。

  1. antialias

建立 Renderer 時:

const renderer =
    new THREE.WebGLRenderer({
        antialias: true
    });

antialias 是抗鋸齒可以讓球體邊緣看起來比較平滑。

  1. Pixel Ratio

今天另外加入:

renderer.setPixelRatio(
    Math.min(
        window.devicePixelRatio,
        2
    )
);

它可以讓高解析度螢幕上的 Three.js 畫面更清楚。
但我們限制最高為 2 避免解析度太高造成 GPU 額外負擔。
這也是我們第一次開始稍微考慮後面的效能問題。

四、今天小結

Day 9 最大的改變其實不是太陽,而是我們正式把練習畫面改造成最後太陽系會使用的全螢幕。

之前:
image
現在:
image

這個架構接下來就不要再改回 800 × 500。
從 Day 9 開始,我們直接在這個全螢幕場景上持續增加功能。

Day 10 預告

「八大行星:一次建立完整太陽系」

我們會一次加入八大行星,而且不只是單純放八顆球,我們會開始整理程式結構,用行星資料 + 迴圈建立行星,而不是複製貼上八份幾乎相同的程式。
也就是從 Day 10 開始,畫面會真正變成:
太陽 ─ 水星 ─ 金星 ─ 地球 ─ 火星 ─ 木星 ─ 土星 ─ 天王星 ─ 海王星


上一篇
Day 8 - 3D 座標與空間:開始安排行星的位置
下一篇
Day 10 - 八大行星:建立完整的太陽系成員
系列文
從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言