iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Modern Web

從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作系列 第 28 篇

Day 28 - Responsive × Performance:讓太陽系在不同裝置更順暢

  • 分享至 

  • xImage
  •  

現在整個專案已經有相當完整的互動功能。
但功能越來越多之後,也會開始遇到兩個很實際的問題:
不同螢幕尺寸
↓
UI 可能擠在一起

Geometry、Shadow、Pixel Ratio
↓
效能開始受到影響

所以今天不再新增大型功能,而是開始做:
Responsive + Performance Optimization
替 Day 29 的整合 Debug,還有 Day 30 最終完成做準備。

由於目前專案程式碼已逐漸增加,從 Day 23 開始文章只列出當日新增或修改的程式碼。
完整整合版將於 Day 30 最終完成時提供。

一、今天完成目標

今天會完成:

  • 調整 UI 讓小螢幕也能正常顯示
  • 使用 CSS Media Query
  • 調整手機版 Title
  • 調整手機版 Control Panel
  • 調整 Planet Info Panel
  • 限制 devicePixelRatio
  • 降低不必要的 SphereGeometry 分段
  • 保留必要 Shadow
  • 依螢幕尺寸調整 Camera
  • 了解哪些地方會影響 Three.js 效能

二、本日修改摘要

項目 修改內容
CSS 新增 Responsive Media Query
Title 小螢幕縮小字體
Control Panel 手機版縮小並改位置
Planet Info 小螢幕改成底部卡片
Renderer Pixel Ratio 維持最大 2
Geometry Planet Segment 從 64 降成 48
Moon Segment 從 64 降成 32
Resize 小螢幕時重新調整 Camera
Shadow 繼續只保留必要物件

三、為什麼要做 Responsive?

目前我們的 UI 大概是:
左上角 → Title
右上角 → Control Panel
左下角 → Planet Info
桌面螢幕看起來沒問題。
但如果螢幕變成手機、平板、小型筆電
可能就會發生:
Title 太大、Control Panel 太寬、Planet Info 擋住畫面、UI 互相重疊

所以今天會使用:@media
來調整不同螢幕尺寸。

四、先處理 Title

目前:

.title {
    top: 30px;
    left: 40px;
}

.title h1 {
    font-size: 36px;
}

桌面版沒問題,但是手機版 36px 就會有點太大。
所以加入:

@media (
    max-width: 768px
) {

    .title {
        top: 18px;
        left: 18px;
    }

    .title h1 {
        font-size: 24px;
    }

    .title p {
        font-size: 13px;
    }

}

這樣:
Desktop → 36px
Mobile → 24px

五、Control Panel 手機版

目前 Control Panel:

width: 230px;
top: 30px;
right: 30px;

手機上可能太寬所以:

@media (
    max-width: 768px
) {

    .control-panel {
        top: 90px;
        right: 12px;

        width: 190px;

        padding: 14px;
    }

}

這樣可以避免 Title + Control Panel 互相撞在一起。

六、Planet Info 手機版

Desktop 版左下角很適合。
但手機版畫面比較窄,所以可以改成底部左右留一點空間。
CSS:

@media (
    max-width: 768px
) {

    .planet-info {
        left: 12px;
        right: 12px;
        bottom: 12px;

        width: auto;

        padding: 16px;
    }

}

原本 width: 280px;
手機版改成 width: auto;
讓它自動填滿可用空間。

七、Renderer Pixel Ratio

前面我們已經有:

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

這一段其實就是一個很重要的效能優化。
因為有些手機可能:

devicePixelRatio
=
3
4

如果直接:

renderer.setPixelRatio(
    window.devicePixelRatio
);

GPU 需要處理的 Pixel 數量會大量增加。

八、為什麼限制在 2?

假設螢幕:1920 × 1080
Pixel Ratio 1:1920 × 1080
Pixel Ratio 2:3840 × 2160
Pixel Ratio 3:5760 × 3240
差距非常大。
所以:

Math.min(
    window.devicePixelRatio,
    2
)

代表最多使用 2
通常畫面已經很清楚,又不會造成太高負擔。
這段我們繼續保留。

九、SphereGeometry 其實不用全部 64

目前 Planet:

new THREE.SphereGeometry(
    data.size,
    64,
    64
);

這會產生非常多 Vertex。
八顆行星全部使用 64 × 64 其實有點浪費。
因為我們的 Planet 在大部分畫面裡都不算非常大。
所以可以改成:

new THREE.SphereGeometry(
    data.size,
    48,
    48
);

視覺差異通常不會太明顯。

十、Moon 可以更低

Moon 本來只有 size = 0.2 非常小。
所以:

new THREE.SphereGeometry(
    0.2,
    64,
    64
);

其實完全不需要這麼高。改成:

new THREE.SphereGeometry(
    0.2,
    32,
    32
);

就已經很足夠。

十一、Venus Atmosphere 也可以降低

Venus Atmosphere:

new THREE.SphereGeometry(
    data.size * 1.03,
    64,
    64
);

可以改成:

new THREE.SphereGeometry(
    data.size * 1.03,
    48,
    48
);

因為外層主要是 Texture + Transparent
通常不需要太高 Polygon。

十二、Sun 要不要一起降?

Sun 是整個系統中心 + 比較大的球
而且 Camera 有時候會靠近。
所以 Sun 可以先保留 64,64
這樣中心天體仍然比較圓滑。
目前可以採用:

Sun → 64 × 64
Planet → 48 × 48
Venus Atmosphere → 48 × 48
Moon → 32 × 32

這樣比較合理。

十三、Shadow 繼續只保留必要物件

我們前面已經把 Shadow 縮減成:

Earth
Moon
Saturn

這個策略今天不需要改回去。
因為 Shadow 通常是 Three.js 中:
比較昂貴的效果
尤其現在的 Light 是 THREE.PointLight
PointLight Shadow 比 DirectionalLight 更重,因為它需要處理多個方向。
所以目前:

planet.castShadow =
    data.name === "Earth" ||
    data.name === "Saturn";


planet.receiveShadow =
    data.name === "Earth";

繼續保留。

Moon:
moon.castShadow =
    true;

moon.receiveShadow =
    true;

也繼續保留。

十四、Starfield 為什麼不用 2000 個 Mesh?

我們現在使用的是THREE.Points
不是 2000 個 THREE.Mesh 這本身也是一個效能優化。
因為 2000 個 Mesh → 2000 個獨立物件
THREE.Points
→ 一個 Geometry
→ 一次管理大量 Points
所以目前:

const starCount =
    2000;

其實還算合理。
而且你現在的生成範圍是:

100 ~ 140

const radius =
    100 +
    Math.random() *
    40;

今天可以直接保留。

十五、Resize 不只改 Renderer

現在 Resize 已經有:

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

camera.updateProjectionMatrix();

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

這是正確的。
今天可以再加入:

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

確保視窗尺寸改變 Pixel Ratio 也重新確認

十六、小螢幕 Camera

手機畫面比較窄。
如果 Camera 距離和 Desktop 一樣,
Planet 可能會太靠近畫面邊緣。
所以可以建立:

function updateCameraForScreen() {

    if (
        window.innerWidth <=
        768
    ) {

        if (
            !focusMode
        ) {

            camera.position.set(
                45,
                40,
                55
            );

        }

    }

}

然後 updateCameraForScreen();
以及 Resize updateCameraForScreen();

十七、為什麼 focusMode 時不要改 Camera?

注意這個判斷:

if (
    !focusMode
)

如果目前正在 Earth Focus
這時使用者旋轉手機或調整視窗。
如果 Resize Function 直接 camera.position.set(...)
就會把 Planet Focus 破壞掉。
所以只有沒有進入 Focus Mode 時,才重新設定 Camera。

十八、可以建立 Desktop / Mobile Camera

完整一點可以寫:

function updateCameraForScreen() {

    if (
        focusMode
    ) {

        return;

    }


    if (
        window.innerWidth <=
        768
    ) {

        camera.position.set(
            45,
            40,
            55
        );

    } else {

        camera.position.set(
            30,
            25,
            30
        );

    }


    controls.target.set(
        0,
        0,
        0
    );


    controls.update();

}

這樣:
Desktop → 30, 25, 30
Mobile → 45, 40, 55
手機 Camera 稍微往外。

十九、但要小心 Keyboard Camera

我們前面有 四種 Camera Preset。
如果每次 Resize 都執行 updateCameraForScreen();
會把使用者自己選的 Camera Preset 重設掉。
所以今天可以採取比較保守的做法:

頁面第一次載入 → 根據 Screen 設定 Camera
Resize → 只更新 aspect / renderer

這樣比較不會破壞:

Camera Shortcut
Planet Focus
OrbitControls

二十、今天真正需要修改的 Geometry

Planet:

const geometry =
    new THREE.SphereGeometry(
        data.size,
        48,
        48
    );

Venus:

const atmosphereGeometry =
    new THREE.SphereGeometry(
        data.size *
        1.03,
        48,
        48
    );

Moon:

const moonGeometry =
    new THREE.SphereGeometry(
        0.2,
        32,
        32
    );

Sun 仍然:

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

二十一、今天新增的 Responsive CSS

主要加入:

@media (
    max-width: 768px
) {

    .title {
        top: 18px;
        left: 18px;
    }


    .title h1 {
        font-size: 24px;
    }


    .title p {
        font-size: 13px;
    }


    .control-panel {
        top: 90px;
        right: 12px;

        width: 190px;

        padding: 14px;
    }


    .control-panel h2 {
        font-size: 14px;
    }


    .control-panel label,
    .control-panel button {
        font-size: 13px;
    }


    .planet-info {
        left: 12px;
        right: 12px;
        bottom: 12px;

        width: auto;

        padding: 16px;
    }

}

二十三、今天小結

今天除了完成 Responsive 與 Performance Optimization,
最後也可以實際用「手機版」來檢查成果。
如果你是使用 VS Code + Live Server,可以用兩種方式測試。


方法一:使用 Chrome 模擬手機

先在瀏覽器開啟專案。
接著按 F12 開啟 Developer Tools。
再按 Ctrl + Shift + M 切換成 Device Toolbar。
接著就可以選擇 iPhone、Pixel、iPad 等不同裝置尺寸。
這時我們今天加入的:

@media (
    max-width: 768px
)

就會開始生效。
可以檢查

Title 是否縮小
Control Panel 是否跑到適合的位置
Planet Info 是否正確顯示在畫面底部
整個 3D Scene 是否仍然可以正常操作

方法二:直接用手機實機測試

如果想確認真正手機上的操作感,
像是觸控、滑動、縮放、效能、UI 大小
就可以讓手機直接連到電腦上的 Live Server。
首先讓 電腦+手機 連到同一個 Wi-Fi。
接著在 Windows 開啟 終端機(CMD) 輸入 ipconfig
找到電腦的 IPv4 Address 例如 192.168.1.105
假設 Live Server 原本網址是:
http://127.0.0.1:5500/
手機上就改成:
http://192.168.1.105:5500/
就可以直接用手機瀏覽器打開目前的 Three.js 太陽系。
注意 127.0.0.1 代表目前這一台裝置自己。
所以手機不能直接使用 127.0.0.1 而是要使用電腦的區域網路 IP。


今天完成之後,我們不只是讓程式可以執行
而是開始檢查 Desktop + Tablet + Mobile 在不同裝置上是否都能正常操作。
這也是 Responsive Web Design 很重要的一部分。
Day 29 就會進入最後的 Integration + Debug 把目前所有功能一起檢查,
準備 Day 30 的最終完整作品。

Day 29 預告

「Integration × Debug:最終整合與問題修正」

Day 29 不會再加入大型新功能。
我們會把目前所有功能全部重新檢查。
包含:
選中 Planet 後 Orbit 是否正常?
Focus Mode 是否會跟著 Planet?
Pause 時 Focus 是否正常?
Orbit Toggle 是否影響 Highlight?
手機版 UI 是否會遮住 Planet Info?
返回太陽系是否會正確清除 selectedPlanet?
按 1~4 Camera Preset 是否會退出 Focus Mode?

Day 29 的目標就是找 Bug → 修 Bug → 整理程式 → 準備 Day 30 最終作品


上一篇
Day 27 - Planet Focus × Planet Info:鎖定行星與資訊面板
下一篇
Day 29 - Integration × Debug:最終整合與問題修正
系列文
從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言