> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/unity/build/loading-screen-customization.md).

# 加载画面自定义

说明如何自定义 Unity WebGL 加载期间显示的画面。

### 加载画面文件

加载画面存在于两个地方。

| 路径                                        | 作用          |
| ----------------------------------------- | ----------- |
| `WebGLTemplates/AITTemplate/loading.html` | SDK默认模板（原始） |
| `Assets/AppsInToss/loading.html`          | 按项目定制的加载画面  |

`AITPackageInitializer`为 `[InitializeOnLoad]`在编辑器启动时执行， `Assets/AppsInToss/loading.html`如果没有此文件，则复制 SDK 模板。修改此文件即可应用自定义加载画面。

SDK模板搜索顺序：

1. `Packages/im.toss.apps-in-toss-unity-sdk/WebGLTemplates/AITTemplate/loading.html`
2. `Packages/com.appsintoss.miniapp/WebGLTemplates/AITTemplate/loading.html`
3. 基于 Assembly 路径

#### 构建时插入顺序

构建的 `CopyWebGLToPublic()` 阶段 `index.html`中的 `%AIT_LOADING_SCREEN%` 占位符会被替换为完整的加载画面内容。

{% code collapsedlinecount="10" %}

```
1. 存在 Assets/AppsInToss/loading.html？
   → 是：使用项目自定义加载画面
   → 否：回退到 SDK 默认模板

2. 如果 SDK 模板也不存在？
   → Debug.LogWarning("找不到加载画面文件。将使用空白加载画面")
   → 替换为空字符串
```

{% endcode %}

也就是说加载画面会 **在构建时内联到 index.html 中**。由于不会作为独立文件加载，因而相对路径引用会以最终 `index.html` 为基准进行解析。

#### 恢复为默认模板

`AIT > Reset Loading Screen`执行后会弹出确认对话框，然后将 SDK 模板 `Assets/AppsInToss/loading.html`重新复制过来。自定义内容会消失，如有需要请先备份。

#### 文件结构

{% code collapsedlinecount="10" %}

```
Assets/
└── AppsInToss/
    ├── Editor/
    │   └── AITConfig.asset
    └── loading.html    ← 自定义加载画面（存在时自动应用）
```

{% endcode %}

### 应用信息

加载画面中显示的应用信息按以下顺序确定。

1. **原生应用环境** （toss 应用内）— SDK 会 `getAppsInTossGlobals`通过它获取应用信息并覆盖。
2. **回退** （网页浏览器等）— 使用在 AIT Configuration 中设置的值。

| 设置                   | 说明                |
| -------------------- | ----------------- |
| 应用名称（`displayName`)  | 显示在加载画面中的应用名称     |
| 应用图标（`iconUrl`)      | 显示在加载画面中的应用图标 URL |
| 默认颜色（`primaryColor`) | 进度条颜色             |

> **参考**：在实际 toss 应用环境中，原生值优先，因此上述设置主要会在开发·测试环境中看到。

### 可自定义范围

`loading.html`可以自由修改其 HTML、CSS、JavaScript。 `AITLoading` 可以通过 API 接收进度值。接收到的值可以按所需方式自由呈现。

* **UI 设计** — 进度条、饼图、圆形加载等
* **动画** — CSS 动画、JavaScript 动画、GIF、Lottie
* **品牌元素** — 吉祥物角色、Logo 动画
* **交互元素** — 小游戏、提示滑块

#### 使用外部资源

**StreamingAssets** （推荐）— `Assets/StreamingAssets`放在这里会自动包含到构建中。

{% code collapsedlinecount="10" %}

```html
<img src="StreamingAssets/loading-character.gif" />
<link rel="stylesheet" href="StreamingAssets/loading-fonts.css" />
```

{% endcode %}

{% code collapsedlinecount="10" %}

```
Assets/
└── StreamingAssets/
    ├── loading-character.gif
    └── loading-fonts.css
```

{% endcode %}

**数据 URI** — 几 KB 以下的小图片会以内联 Base64 形式嵌入。

{% code collapsedlinecount="10" %}

```html
<img src="data:image/png;base64,iVBORw0KGgo..." />
```

{% endcode %}

**CDN** — 通过外部 URL 加载。会产生网络依赖，且加载画面本身可能出现得更晚。

{% code collapsedlinecount="10" %}

```html
<img src="https://your-cdn.com/loading-character.gif" />
```

{% endcode %}

### AITLoading API

`window.AITLoading`在 `index.html`中定义，以下六项就是全部公开接口。 `_`以

| 开头的成员属于内部实现，请勿依赖。      | 说明                                       |
| ---------------------- | ---------------------------------------- |
| `appInfo`              | `{ iconUrl, displayName, primaryColor }` |
| `onReady(callback)`    | 应用信息准备完成                                 |
| `onProgress(callback)` | 进度更新                                     |
| `onComplete(callback)` | 加载完成                                     |
| `onError(callback)`    | 发生错误                                     |
| `hide()`               | 隐藏加载画面                                   |

#### appInfo

{% code collapsedlinecount="10" %}

```javascript
console.log(AITLoading.appInfo.iconUrl);       // 应用图标 URL
console.log(AITLoading.appInfo.displayName);   // 应用显示名称
console.log(AITLoading.appInfo.primaryColor);  // 默认颜色
```

{% endcode %}

初始值是构建时替换的 Configuration 值，当原生应用信息到达后会更新为其值。

#### onReady

在应用信息准备好后调用。用于 UI 初始化。

{% code collapsedlinecount="10" %}

```javascript
AITLoading.onReady(function(appInfo) {
    document.getElementById('app-icon').src = appInfo.iconUrl;
    document.getElementById('app-name').textContent = appInfo.displayName;
});
```

{% endcode %}

> **重要**: `onReady` 回调 **不要假定只会调用一次。** 初始化时会调用一次，之后如果原生应用信息到达，更新后的 `appInfo`值会再次调用。请编写幂等的回调，使其多次执行也安全。如果在已经完成初始化后才注册，则会立即调用一次。

#### onProgress

`0.0`到 `1.0` 之间的进度值。

{% code collapsedlinecount="10" %}

```javascript
AITLoading.onProgress(function(progress) {
    console.log('加载进度：', Math.round(progress * 100) + '%');
});
```

{% endcode %}

#### onComplete

加载结束时调用。如果在已完成后才注册，则会立即调用。

{% code collapsedlinecount="10" %}

```javascript
AITLoading.onComplete(function() {
    AITLoading.hide();
});
```

{% endcode %}

#### onError

`{ message }` 形式的对象。

{% code collapsedlinecount="10" %}

```javascript
AITLoading.onError(function(error) {
    console.error('加载失败：', error.message);
});
```

{% endcode %}

> **参考**：WebGL 上下文创建失败（`GLctx`, `WebGL 上下文`, `无法创建` 系列）由 SDK 通过专用路径处理，因此不会进入此回调。除非你要直接处理设备无法打开 WebGL 的情况，否则可以不用在意。

#### hide

`#ait-loading-wrapper` 元素通过 `display: none`隐藏。

{% code collapsedlinecount="10" %}

```javascript
AITLoading.hide();
```

{% endcode %}

### 示例

下面是我手写的示例。SDK 实际提供的默认模板（深色主题）请在 `Assets/AppsInToss/loading.html`中查看。

#### 进度条

{% code collapsedlinecount="10" %}

```html
<style>
    /* ===== 可自定义的 CSS 变量 ===== */
    :root {
        --loading-bg: #ffffff;
        --title-color: #191f28;
        --app-name-color: #333d4b;
        --progress-bg: #e5e8eb;
        --icon-size: 30px;
        --progress-height: 5px;
    }

    .loading-container {
        position: fixed;
        inset: 0;
        background: var(--loading-bg);
        display: flex;
        flex-direction: column;
        padding: 120px 20px 0;
        font-family: -apple-system, BlinkMacSystemFont, sans-serif;
    }

    .loading-title {
        font-size: 22px;
        font-weight: 600;
        color: var(--title-color);
        line-height: 1.4;
        margin-bottom: 44px;
    }

    .loading-card {
        padding: 16px;
        border: 1px solid #e5e8eb;
        border-radius: 16px;
    }

    .loading-header {
        display: flex;
        align-items: center;
        margin-bottom: 12px;
    }

    .loading-icon {
        width: var(--icon-size);
        height: var(--icon-size);
        border-radius: 8px;
        background: rgba(2, 32, 71, 0.05);
        overflow: hidden;
    }

    .loading-icon img { width: 100%; height: 100%; object-fit: cover; }

    .loading-app-name {
        margin-left: 12px;
        font-size: 15px;
        font-weight: 500;
        color: var(--app-name-color);
    }

    .loading-progress {
        height: var(--progress-height);
        background: var(--progress-bg);
        border-radius: 2.5px;
        overflow: hidden;
    }

    .loading-progress-bar {
        height: 100%;
        width: 0%;
        transition: width 0.3s ease;
    }
</style>

<div class="loading-container" id="ait-loading">
    <div class="loading-title" id="loading-title"></div>
    <div class="loading-card">
        <div class="loading-header">
            <div class="loading-icon"><img id="app-icon" src="" alt="" /></div>
            <div class="loading-app-name" id="app-name"></div>
        </div>
        <div class="loading-progress">
            <div class="loading-progress-bar" id="progress-bar"></div>
        </div>
    </div>
</div>

<script>
(function() {
    // 使用应用信息初始化 UI（当原生信息到达时可能会再次调用）
    AITLoading.onReady(function(appInfo) {
        document.getElementById('app-icon').src = appInfo.iconUrl || '';
        document.getElementById('app-name').textContent = appInfo.displayName || '';
        document.getElementById('progress-bar').style.background =
            appInfo.primaryColor || '#3182f6';
    });

    // 更新进度
    AITLoading.onProgress(function(progress) {
        document.getElementById('progress-bar').style.width = (progress * 100) + '%';
    });

    // 加载完成时隐藏画面
    AITLoading.onComplete(function() {
        AITLoading.hide();
    });
})();
</script>
```

{% endcode %}

#### 百分比显示与错误处理

{% code collapsedlinecount="10" %}

```html
<style>
    :root {
        --loading-bg: #ffffff;
        --text-color: #191f28;
        --sub-text-color: #6b7684;
    }

    .loading-container {
        position: fixed;
        inset: 0;
        background: var(--loading-bg);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        font-family: -apple-system, BlinkMacSystemFont, sans-serif;
    }

    .loading-icon { width: 80px; height: 80px; border-radius: 20px; margin-bottom: 24px; }
    .loading-name { font-size: 18px; font-weight: 600; color: var(--text-color); }
    .loading-progress { width: 200px; height: 6px; background: #e5e8eb; border-radius: 3px; margin-top: 24px; overflow: hidden; }
    .loading-progress-bar { height: 100%; width: 0%; transition: width 0.3s ease; }
    .loading-percent { margin-top: 12px; font-size: 14px; color: var(--sub-text-color); }
</style>

<div class="loading-container" id="ait-loading">
    <img class="loading-icon" id="app-icon" alt="" />
    <div class="loading-name" id="app-name"></div>
    <div class="loading-progress"><div class="loading-progress-bar" id="progress-bar"></div></div>
    <div class="loading-percent" id="percent-text">0%</div>
</div>

<script>
(function() {
    AITLoading.onReady(function(appInfo) {
        document.getElementById('app-icon').src = appInfo.iconUrl || '';
        document.getElementById('app-name').textContent = appInfo.displayName || '';
        document.getElementById('progress-bar').style.background = appInfo.primaryColor || '#3182f6';
    });

    AITLoading.onProgress(function(progress) {
        var percent = Math.round(progress * 100);
        document.getElementById('progress-bar').style.width = percent + '%';
        document.getElementById('percent-text').textContent = percent + '%';
    });

    AITLoading.onComplete(function() {
        AITLoading.hide();
    });

    AITLoading.onError(function(error) {
        document.getElementById('percent-text').textContent = '加载失败';
        document.getElementById('percent-text').style.color = '#f04452';
    });
})();
</script>
```

{% endcode %}

### 故障排查

#### 图标未显示

1. 请检查 AIT Configuration 中是否设置了图标 URL
2. 可能会因 CORS 策略而阻止外部图片——建议使用同域图片
3. 在原生应用环境中，应用图标会自动加载，因此可能看不到回退值

#### 自定义加载画面未应用

1. 文件是否在 `Assets/AppsInToss/loading.html`中——其他路径不会被识别
2. 加载画面会在构建时内联，因此只修改文件而不重新构建不会生效

#### 进度未更新

1. `AITLoading.onProgress()`是否已注册
2. 回调应在页面加载早期注册——如果在加载已经开始后注册，将无法接收到之前的进度

#### appInfo 为空

`AITLoading.appInfo`不要直接读取， `onReady` 而应在回调中使用。因为在脚本执行时，应用信息初始化可能尚未完成。

### 相关文档

* [构建流水线](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-process) — `%AIT_LOADING_SCREEN%` 发生替换的地方
* [构建自定义](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization) — 修改加载画面之外的网页入口
* [开始使用](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/getting-started) — 应用信息设置
* [问题解决](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/faq) — 构建与运行时整体


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/unity/build/loading-screen-customization.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
