@ericchen1990/pano-view 3.4.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +615 -73
- package/README.zh-CN.md +583 -71
- package/dist/data.cjs +1080 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.mts +581 -0
- package/dist/data.d.ts +581 -0
- package/dist/data.mjs +1057 -0
- package/dist/data.mjs.map +1 -0
- package/dist/index.cjs +9120 -8431
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +904 -625
- package/dist/index.d.ts +904 -625
- package/dist/index.mjs +9273 -8578
- package/dist/index.mjs.map +1 -1
- package/dist/ui/styles.css +70 -0
- package/dist/ui.cjs +195 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.mts +63 -0
- package/dist/ui.d.ts +63 -0
- package/dist/ui.mjs +152 -0
- package/dist/ui.mjs.map +1 -0
- package/docs/dialogs.md +606 -0
- package/docs/dialogs.zh-CN.md +606 -0
- package/package.json +20 -5
package/README.zh-CN.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @ericchen1990/pano-view
|
|
1
|
+
# @ericchen1990/pano-view · [](./LICENSE) [](https://www.npmjs.com/package/@ericchen1990/pano-view) [](https://github.com/Eric-Chen1990/pano-view/actions/workflows/ci.yml) [](https://github.com/Eric-Chen1990/pano-view/pulls)
|
|
2
2
|
|
|
3
3
|
[English](./README.md) | **简体中文**
|
|
4
4
|
|
|
@@ -6,23 +6,341 @@
|
|
|
6
6
|
|
|
7
7
|
在[在线 playground](https://pano-view-playground.vercel.app/?utm_source=github&utm_medium=readme&utm_campaign=package-readme&utm_content=playground) 中试用这些组件。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [pano-converter](#pano-converter)
|
|
12
|
+
- [数据来源与选型](#数据来源与选型)
|
|
13
|
+
- [静态项目与 JSON 数据](#静态项目与-json-数据)
|
|
14
|
+
- [常见问题与故障排查](#常见问题与故障排查)
|
|
15
|
+
- [兼容 krpano 的瓦片输出](#兼容-krpano-的瓦片输出)
|
|
16
|
+
- [安装](#安装)
|
|
17
|
+
- [示例](#示例)
|
|
18
|
+
- [导出组件](#导出组件)
|
|
19
|
+
- [PanoViewer](#panoviewer)
|
|
20
|
+
- [DOM Overlay 与弹窗](#dom-overlay-与弹窗)
|
|
21
|
+
- [Sphere](#sphere)
|
|
22
|
+
- [PanoVideo](#panovideo)
|
|
23
|
+
- [Tile](#tile)
|
|
24
|
+
- [Scenes](#scenes)
|
|
25
|
+
- [PanoFilter](#panofilter)
|
|
26
|
+
- [AutoRotate](#autorotate)
|
|
27
|
+
- [BackgroundAudio](#backgroundaudio)
|
|
28
|
+
- [Gyro](#gyro)
|
|
29
|
+
- [WebVR](#webvr)
|
|
30
|
+
- [鼠标、触控与键盘控件](#鼠标触控与键盘控件)
|
|
31
|
+
- [全景坐标事件](#全景坐标事件)
|
|
32
|
+
- [PanoEvents](#panoevents)
|
|
33
|
+
- [PanoContextMenu](#panocontextmenu)
|
|
34
|
+
- [共享热点约定与可序列化定义](#共享热点约定与可序列化定义)
|
|
35
|
+
- [ImageHotspot](#imagehotspot)
|
|
36
|
+
- [GraphicHotspot](#graphichotspot)
|
|
37
|
+
- [热点模式与缩放行为](#热点模式与缩放行为)
|
|
38
|
+
- [PolygonHotspot](#polygonhotspot)
|
|
39
|
+
- [PolylineHotspot](#polylinehotspot)
|
|
40
|
+
- [SequenceHotspot](#sequencehotspot)
|
|
41
|
+
- [VideoHotspot](#videohotspot)
|
|
42
|
+
- [AudioHotspot](#audiohotspot)
|
|
43
|
+
- [TextHotspot](#texthotspot)
|
|
44
|
+
- [IframeHotspot](#iframehotspot)
|
|
45
|
+
- [Next.js 与 SSR](#nextjs-与-ssr)
|
|
46
|
+
- [球面多分辨率瓦片](#球面多分辨率瓦片)
|
|
47
|
+
- [无障碍颜色矩阵与标签排版](#无障碍颜色矩阵与标签排版)
|
|
48
|
+
|
|
49
|
+
## pano-converter
|
|
50
|
+
|
|
51
|
+
[@ericchen1990/pano-converter](../converter/README.zh-CN.md) 是本仓库中的另一个包。它在 Node.js 中,以及通过 `pano-convert` CLI,生成立方体与等距圆柱瓦片金字塔,并返回可供 [`Tile`](#tile)、[`SphereTile`](#球面多分辨率瓦片) 与 [`Scenes`](#scenes) 使用的 `viewerConfig`。转换器不依赖 React。
|
|
52
|
+
|
|
53
|
+
## 数据来源与选型
|
|
54
|
+
|
|
55
|
+
查看器接收配置对象,或通过 HTTP 加载 JSON,不直接连接数据库。数据库连接、查询、鉴权和响应映射由宿主后端负责。数据库 API 也可以返回 JSON;这里比较的是静态文件和动态提供的配置,而不是 JSON 格式本身。两种方式的预览图、瓦片和媒体都可以使用对象存储/CDN。
|
|
56
|
+
|
|
57
|
+
| 维度 | 静态 JSON + CDN | 数据库 + 后端 API |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| 配置读取 | 无需查询数据库;仍有存储、请求和流量成本 | 到达后端的请求需要处理,可能查询数据库;缓存可以减少这些开销 |
|
|
60
|
+
| 更新 | 生成并发布文件,再管理入口引用和缓存 | 无需发布静态文件;生效时间仍取决于后端和客户端缓存 |
|
|
61
|
+
| 权限与个性化 | 公开文件适合公开内容;私有分发需要托管/CDN 访问控制 | 后端可按用户、组织鉴权并返回不同配置 |
|
|
62
|
+
| 可用性与维护 | 依赖静态托管/CDN;维护发布及缓存更新流程 | 依赖 API 及其数据库/缓存服务;维护查询、鉴权及缓存失效 |
|
|
63
|
+
| 适用场景 | 公开、已发布的导览;更新较少;读取流量大 | 编辑预览、私有内容、频繁更新和个性化 |
|
|
64
|
+
|
|
65
|
+
**公开、已发布的导览优先使用 JSON + CDN;编辑预览、私有和个性化内容优先使用数据库 + API。** 同时需要两者时,以数据库作为编辑来源,发布时生成 JSON 快照。这种混合方式需要应用自己的发布流程;包不会自动生成或同步快照。
|
|
66
|
+
|
|
67
|
+
两条路径均不会自动轮询或订阅配置变化。更新已打开的页面时,宿主应用需要重新加载配置,并将新数据传给查看器。未命中缓存的 API 读取可以返回当前数据库值,但不会让已有查看器自动更新。
|
|
68
|
+
|
|
69
|
+
### 数据库配置接入
|
|
70
|
+
|
|
71
|
+
后端先查询导览并完成鉴权,再将需要公开给查看器的配置字段映射为项目。下例的 `readAuthorizedTour` 由宿主应用实现,负责数据库读取和访问检查,不是包提供的 API。它返回的场景记录包含转换时保存的精简 `viewerConfig`、资源目录、视角和热点定义:
|
|
10
72
|
|
|
11
|
-
|
|
73
|
+
```ts
|
|
74
|
+
import { loadPanoProject } from "@ericchen1990/pano-view/data";
|
|
75
|
+
import { readAuthorizedTour } from "./tour-repository";
|
|
76
|
+
|
|
77
|
+
export async function loadTourFromDatabase(tourId: string) {
|
|
78
|
+
const tour = await readAuthorizedTour(tourId);
|
|
79
|
+
return loadPanoProject({
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
initialSceneId: tour.initialSceneId,
|
|
82
|
+
scenes: tour.scenes.map((scene) => ({
|
|
83
|
+
id: scene.id,
|
|
84
|
+
title: scene.title,
|
|
85
|
+
source: {
|
|
86
|
+
viewerConfig: scene.viewerConfig,
|
|
87
|
+
baseUrl: scene.assetBaseUrl,
|
|
88
|
+
},
|
|
89
|
+
initialView: scene.initialView,
|
|
90
|
+
hotspots: scene.hotspots,
|
|
91
|
+
})),
|
|
92
|
+
}, { baseUrl: tour.assetBaseUrl });
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
对象入口必须提供 `baseUrl`:这里的 `tour.assetBaseUrl` 是绝对目录 URL,例如 `https://cdn.example.com/tours/123/v12/`。项目中相对的热点地址基于该目录解析;每个场景可用 `source.baseUrl` 覆盖全景瓦片/预览图目录。全部场景内嵌 `source.viewerConfig` 时,该调用不会请求项目或 viewer JSON;渲染时仍会加载预览图、瓦片和媒体。服务端数据工具从 `@ericchen1990/pano-view/data` 导入。
|
|
97
|
+
|
|
98
|
+
将返回的 `LoadedPanoProject` 传给查看器组件;在 Next.js 中,查看器需位于客户端边界:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
"use client";
|
|
102
|
+
|
|
103
|
+
import {
|
|
104
|
+
PanoViewer, ProjectScenes, type LoadedPanoProject,
|
|
105
|
+
} from "@ericchen1990/pano-view";
|
|
106
|
+
|
|
107
|
+
export function DatabaseTour({ project }: { project: LoadedPanoProject }) {
|
|
108
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
109
|
+
<ProjectScenes project={project} transition="dissolve" />
|
|
110
|
+
</PanoViewer>;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
也可以由已鉴权的 JSON 接口返回 `PanoProjectDefinition`,再调用 `loadPanoProject("https://api.example.com/tours/123", { baseUrl: "https://cdn.example.com/tours/123/v12/" })`;地址不必以 `.json` 结尾。单场景可用 `sceneFromViewerConfig` 处理已有配置,或用 `loadSceneFromJson` 加载返回 `schemaVersion: 1` viewer 文档的 API,再通过 `Scenes` 渲染。请求选项与错误处理见 [静态项目与 JSON 数据](#静态项目与-json-数据)。私有图片/媒体需单独保护:JSON 请求的认证信息不会传到纹理请求。
|
|
115
|
+
|
|
116
|
+
### 缓存与发布更新
|
|
117
|
+
|
|
118
|
+
`project.json`(场景组织、视角和热点)、各个 `viewer.json`(全景渲染配置)和预览图/瓦片/媒体是分别缓存的资源。只刷新项目文件,不会同步刷新地址不变的场景配置或图片。数据库 API 同样可能被后端、CDN 和客户端缓存,因此两种方式都需要缓存策略。
|
|
119
|
+
|
|
120
|
+
- **优先使用版本化目录:** 将完整版本发布到新路径,例如 `/tours/123/v12/`,包含 `project.json`、场景配置和资源;全部上传完成后,再切换页面或入口引用。变化的资源也要更新版本,并在旧配置仍可能被使用期间保留旧资源。内容固定的版本化文件可用长期缓存;选择版本的可变页面或入口需要短缓存或重新验证。AWS 也[建议使用版本化文件名或目录](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/UpdatingExistingObjects.html)。
|
|
121
|
+
- **覆盖相同 URL 时:** 等待设定的缓存有效期结束、要求重新验证,或上传后对受影响路径执行 CDN 失效。CDN 失效不会清除浏览器已有文件;新配置的响应头也无法追溯替换仍在有效期内的缓存响应。版本化路径可避免依赖这些副本过期。`?v=12` 等查询参数仅在 CDN 将其纳入缓存键时有效;JSON 的查询参数不会自动继承到引用的资源。
|
|
122
|
+
- **按资源选择响应头:** `Cache-Control: no-cache` 允许存储,但复用前必须验证;`no-store` 要求缓存不存储响应。可变的公开入口可配合 `ETag` 等验证器重新验证;私有/个性化 API 响应应明确配置策略,例如 `private, no-store`,避免在用户间共享。还需要配置托管/CDN 规则以遵循预期策略。参见 [MDN HTTP 缓存](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching)和 [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control)。
|
|
123
|
+
|
|
124
|
+
这些响应头和 CDN 操作由托管平台/后端配置。loader 的 `requestInit` 仅支持 `headers`、`credentials` 和 `signal`,不提供 Fetch 的 `cache` 选项,也不会使 CDN 缓存失效或自动刷新已加载的项目。
|
|
125
|
+
|
|
126
|
+
## 静态项目与 JSON 数据
|
|
127
|
+
|
|
128
|
+
无需数据库。转换器生成的 `manifest.json` 用于归档和还原;`viewer.json` 只保存渲染配置;应用编写 `project.json` 组织场景、视角和热点。不要把完整清单直接传给 `Scenes`。
|
|
129
|
+
|
|
130
|
+
例如,将下面的 `project.json`、两个转换输出目录和你的 React 应用部署为静态网站:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"schemaVersion": 1,
|
|
135
|
+
"initialSceneId": "entrance",
|
|
136
|
+
"scenes": [
|
|
137
|
+
{
|
|
138
|
+
"id": "entrance",
|
|
139
|
+
"source": { "viewerUrl": "./panoramas/entrance/viewer.json" },
|
|
140
|
+
"initialView": { "yaw": 0, "pitch": 0, "fov": 75 },
|
|
141
|
+
"hotspots": [{
|
|
142
|
+
"id": "to-hall", "type": "graphic", "label": "Enter hall",
|
|
143
|
+
"position": { "yaw": 30, "pitch": -10 },
|
|
144
|
+
"graphic": { "kind": "arrow" },
|
|
145
|
+
"action": { "type": "scene", "sceneId": "hall" }
|
|
146
|
+
}]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "hall",
|
|
150
|
+
"source": { "viewerUrl": "./panoramas/hall/viewer.json" },
|
|
151
|
+
"hotspots": [{
|
|
152
|
+
"id": "caption", "type": "text",
|
|
153
|
+
"position": { "yaw": 0, "pitch": 5 }, "text": "Welcome"
|
|
154
|
+
}]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`source` 必须在 `viewerUrl` 与内嵌 `viewerConfig` 中二选一,可加 `baseUrl` 覆盖该场景的全景资源根目录。`title`、`initialView`、`hotspots` 可省略。未指定 `initialSceneId` 时使用第一项。场景 ID 全项目唯一,热点 ID 在各场景内唯一,跳转目标必须存在。
|
|
161
|
+
|
|
162
|
+
`loadPanoProject` 读取项目及各场景的轻量配置,以最多四个并发请求加载元数据;任一失败会整体报错,不加载所有纹理。返回的 `LoadedPanoProject` 包含 `initialSceneId` 和 `scenes`;每项保存运行时 `scene`、标题、初始视角和已解析资源地址的热点。可直接传给 `ProjectScenes`:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { useEffect, useState } from "react";
|
|
166
|
+
import {
|
|
167
|
+
PanoViewer, ProjectScenes, loadPanoProject, PanoDataError,
|
|
168
|
+
type LoadedPanoProject,
|
|
169
|
+
} from "@ericchen1990/pano-view";
|
|
170
|
+
|
|
171
|
+
export function Tour() {
|
|
172
|
+
const [project, setProject] = useState<LoadedPanoProject>();
|
|
173
|
+
const [error, setError] = useState<string>();
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const controller = new AbortController();
|
|
176
|
+
loadPanoProject("/tour/project.json", {
|
|
177
|
+
requestInit: { signal: controller.signal },
|
|
178
|
+
}).then((result) => {
|
|
179
|
+
if (!controller.signal.aborted) setProject(result);
|
|
180
|
+
}).catch((cause: unknown) => {
|
|
181
|
+
if (!controller.signal.aborted) {
|
|
182
|
+
setError(cause instanceof PanoDataError
|
|
183
|
+
? `${cause.code}: ${cause.message}` : String(cause));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
return () => controller.abort();
|
|
187
|
+
}, []);
|
|
188
|
+
if (error) return <p role="alert">{error}</p>;
|
|
189
|
+
if (!project) return <p role="status">Loading tour…</p>;
|
|
190
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
191
|
+
<ProjectScenes project={project} transition="dissolve" />
|
|
192
|
+
</PanoViewer>;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`ProjectScenes` 放在 `PanoViewer` 内,复用全部九类现有热点。`action: { type: "scene", sceneId }` 点击后切换场景;没有 action 的音频、视频、序列热点点击切换播放。离开场景时卸载媒体,再次进入恢复 JSON 初始播放配置。浏览器仍可能阻止自动播放。
|
|
197
|
+
|
|
198
|
+
配置了 `initialView` 的场景在 `onSceneReady` 时应用视角,缺省字段为 yaw 0、pitch 0、fov 75;未配置时保持当前视角。过渡期间热点隐藏。使用 `activeSceneId` / `onActiveSceneIdChange` 可控制当前场景。`onHotspotClick(event)` 中调用 `event.preventDefault()` 可取消默认动作;`onHotspotError` 提供场景、热点和原始错误;媒体的原始错误可能是带有 `error` 字段的事件。`renderHotspot(hotspot, context)` 返回 `undefined` 使用默认渲染,返回 `null` 隐藏;context 包含 `sceneId`、`playing`、`onClick`、`togglePlayback`、`reportError` 和 `renderDefault`。
|
|
199
|
+
|
|
200
|
+
### 热点动作与共享弹窗内容
|
|
201
|
+
|
|
202
|
+
`ProjectHotspotAction` 是可辨别联合类型。场景跳转保持 `{ type: "scene", sceneId }`;`{ type: "view", view: { yaw, pitch, fov }, duration?: number }` 调整当前场景视角,默认动画 700 毫秒,0 表示立即应用;`{ type: "popup", popupId }` 请求共享内容。读取 `sceneId` 前先判断 `action.type`。显式动作优先于媒体播放,三种动作均可由 `onHotspotClick(event).preventDefault()` 取消。
|
|
203
|
+
|
|
204
|
+
项目新增可选 `popups: ProjectPopupDefinition[]`。每项包含唯一 `id`、非空 `title` 和有序 `blocks`;内容块 ID 在弹窗内唯一:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"id": "story",
|
|
209
|
+
"title": "About this place",
|
|
210
|
+
"blocks": [
|
|
211
|
+
{ "id": "intro", "type": "text", "text": "Plain text, including line breaks." },
|
|
212
|
+
{ "id": "photo", "type": "image", "src": "media/photo.webp", "alt": "The main hall" },
|
|
213
|
+
{ "id": "film", "type": "video", "src": "media/movie.mp4", "poster": "media/photo.webp" },
|
|
214
|
+
{ "id": "voice", "type": "audio", "src": "media/story.mp3" }
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
内容结构存在 `project.json`,媒体保留为独立文件。弹窗路径相对项目目录解析,不跟随各全景的 `viewer.json`。`loadPanoProject` 返回解析后的 `popups`;新增可选 `resolvePopupAssetUrl(path, context)`,上下文是 `{ defaultUrl, popupId, blockId, field }`,field 为 `src` 或 `poster`;返回绝对 URL 或站点根路径。原有 `resolveAssetUrl` 回调继续只处理场景/热点资源。自定义加载器也可使用 `/data` 导出的 `resolveProjectPopupAssets`。JSON 请求的凭证不会自动保护媒体资源。
|
|
220
|
+
|
|
221
|
+
`ProjectScenes.onPopupRequest({ sceneId, hotspot, popup })` 将展示交给宿主 Overlay。缺少处理器或目标弹窗时通过 `onHotspotError` 报错。独立 UI 入口提供 `ProjectPopupDialog`,查看器主入口不引入 Dialog 依赖:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { useState } from "react";
|
|
225
|
+
import { PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
226
|
+
import { ProjectPopupDialog } from "@ericchen1990/pano-view/ui";
|
|
227
|
+
import "@ericchen1990/pano-view/ui/styles.css";
|
|
228
|
+
|
|
229
|
+
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
230
|
+
const [popup, setPopup] = useState<ProjectPopupDefinition | null>(null);
|
|
231
|
+
return <PanoViewer renderOverlay={() => (
|
|
232
|
+
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
233
|
+
)}>
|
|
234
|
+
<ProjectScenes project={project} onPopupRequest={({ popup }) => setPopup(popup)}
|
|
235
|
+
onActiveSceneIdChange={() => setPopup(null)} />
|
|
236
|
+
</PanoViewer>;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`ProjectPopupDialog` 接收 `popup`(null 关闭)、`onOpenChange`,以及可选 `closeLabel` 和 `onMediaError(block)`。文字按纯文本展示,音视频使用原生控件且不自动播放;关闭或切换内容时释放媒体。全屏、尺寸和焦点行为复用 `PanoDialog`。需要自定义渲染时可替换成自己的 Overlay。
|
|
241
|
+
|
|
242
|
+
继续使用 schemaVersion 1,旧项目仍可读取,包含新动作的项目需使用支持它们的新版查看器。`parsePanoProject(input, { draft: true })` 仅用于编辑草稿,允许空场景列表及未完成的弹窗标题/媒体;加载、保存和导出仍严格校验。宿主可将同一份文档存入数据库或映射到表,查看器不直接连接数据库。
|
|
243
|
+
|
|
244
|
+
单图和已有配置可用纯数据入口;Next.js 的查看器组件需位于客户端边界,服务端加载数据则使用 `/data`:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import {
|
|
248
|
+
sceneFromViewerConfig, loadSceneFromJson, loadPanoProject,
|
|
249
|
+
} from "@ericchen1990/pano-view/data";
|
|
250
|
+
|
|
251
|
+
// A JSON API and a different asset/CDN origin.
|
|
252
|
+
const scene = await loadSceneFromJson(
|
|
253
|
+
"https://api.example.com/rooms/123/viewer",
|
|
254
|
+
{ id: "room", baseUrl: "https://cdn.example.com/rooms/123/" },
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Existing Node.js conversion results need no extra JSON request.
|
|
258
|
+
const inlineScene = sceneFromViewerConfig(result.viewerConfig, {
|
|
259
|
+
id: "room", baseUrl: "https://cdn.example.com/rooms/123/",
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Use source.viewerConfig instead of source.viewerUrl to inline scene data.
|
|
263
|
+
const project = await loadPanoProject({
|
|
264
|
+
schemaVersion: 1,
|
|
265
|
+
scenes: [{ id: "room", source: { viewerConfig: result.viewerConfig } }],
|
|
266
|
+
}, { baseUrl: "https://cdn.example.com/rooms/123/" });
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 地址与上传映射
|
|
270
|
+
|
|
271
|
+
| 引用 | 默认基准 |
|
|
272
|
+
| --- | --- |
|
|
273
|
+
| 项目中的 `viewerUrl`、热点资源及 `source.baseUrl` | 项目根目录 |
|
|
274
|
+
| 外部 `viewer.json` 的瓦片与预览 | 该 JSON 的目录 |
|
|
275
|
+
| 内嵌配置的瓦片与预览 | 项目根目录 |
|
|
276
|
+
| 设置 `source.baseUrl` 的场景资源 | 指定目录 |
|
|
277
|
+
|
|
278
|
+
URL 入口以重定向后最终地址所在目录为默认根目录;对象入口必须提供 `baseUrl`,可用它覆盖项目根目录。目录末尾斜杠自动统一,域名和云前缀无需写回转换 JSON。浏览器接受相对入口地址,Node.js 需要绝对 HTTP(S) 地址。完整资源 URL 不重新拼接,`/` 开头的地址相对资源基准的 origin 解析。
|
|
279
|
+
|
|
280
|
+
上传时若改名或打散目录,使用同步 `resolveAssetUrl(path, context)`。context 提供 `defaultUrl`、`kind`(`tile`、`preview` 或 `hotspot`)、`sceneId` 和可选 `hotspotId`。返回最终的完整 URL 或站点根路径;不会再次拼接。`uploadedUrls` 由业务提供:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const project = await loadPanoProject("/tour/project.json", {
|
|
284
|
+
resolveAssetUrl(path, context) {
|
|
285
|
+
// Map keys must distinguish scenes and, for hotspot assets, hotspot IDs.
|
|
286
|
+
const key = `${context.sceneId}/${context.hotspotId ?? ""}/${path}`;
|
|
287
|
+
return uploadedUrls[key] ?? context.defaultUrl;
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
热点映射涵盖图片、SVG 图片、音频数组、视频 poster、播放图标和 tooltip 图片;文本、CSS 和 SVG path 不作为地址处理。临时签名应由业务预先取得并通过映射返回,不要在每个瓦片请求中异步签名。
|
|
293
|
+
|
|
294
|
+
`requestInit` 只接受 `headers`、`credentials`、`signal`,仅用于 JSON 请求。它们不会传到纹理请求,JSON 地址的查询参数也不会自动继承。`parsePanoProject(unknown)` 可单独校验并复制配置。`sceneFromViewerConfig` 接受未包装的精简配置;`loadSceneFromJson` 接受带 `schemaVersion: 1` 的 `viewer.json`。
|
|
295
|
+
|
|
296
|
+
`PanoDataError` 提供稳定 `code`:`INVALID_OPTIONS`、`FETCH_FAILED`、`HTTP_ERROR`、`INVALID_JSON`、`INVALID_CONFIG`、`UNSUPPORTED_SCHEMA_VERSION`、`UNSUPPORTED_PROJECTION`、`INVALID_PROJECT`、`INVALID_REFERENCE`、`ABORTED`;HTTP 错误带 `status`,项目错误尽可能带 `sceneId` 和 `field`。加载成功仅代表配置就绪,纹理就绪仍由 `onSceneReady` 等回调表示。
|
|
297
|
+
|
|
298
|
+
完整静态示例见 playground 的 `/project` 页面和 `public/fixtures/projects/tour/project.json`。
|
|
299
|
+
|
|
300
|
+
## 兼容 krpano 的瓦片输出
|
|
301
|
+
|
|
302
|
+
当你需要围绕等距圆柱图像或现有 krpano 立方体瓦片金字塔构建 React 原生查看器时,请使用本包。将 krpano `<cube>` 的 `url` 与 `multires` 属性复制到 `Tile` 的 `urlTemplate` 与 `multires`,并将 `<preview url>` 复制到 `previewUrl`;krpano 的磁盘命名与本包默认不同,省略这些值会加载错误的瓦片或预览图。渲染、控件、热点与场景过渡仍由你的 React 应用负责。本包兼容该瓦片输出格式,但与 krpano 无隶属关系。
|
|
12
303
|
|
|
13
304
|
## 安装
|
|
14
305
|
|
|
15
306
|
### 包
|
|
16
307
|
|
|
308
|
+
<details name="pano-view-install" open>
|
|
309
|
+
<summary>npm</summary>
|
|
310
|
+
|
|
17
311
|
```bash
|
|
18
312
|
npm install @ericchen1990/pano-view
|
|
19
313
|
```
|
|
20
314
|
|
|
21
|
-
|
|
315
|
+
</details>
|
|
316
|
+
<details name="pano-view-install">
|
|
317
|
+
<summary>pnpm</summary>
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
pnpm add @ericchen1990/pano-view
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
</details>
|
|
324
|
+
<details name="pano-view-install">
|
|
325
|
+
<summary>yarn</summary>
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
yarn add @ericchen1990/pano-view
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
</details>
|
|
332
|
+
<details name="pano-view-install">
|
|
333
|
+
<summary>bun</summary>
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
bun add @ericchen1990/pano-view
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
</details>
|
|
22
340
|
|
|
23
341
|
### 对等依赖
|
|
24
342
|
|
|
25
|
-
|
|
343
|
+
**宿主应用**必须提供这些包的兼容版本。对等依赖是否自动安装取决于包管理器及其配置;缺失的依赖应按下列版本范围显式安装。
|
|
26
344
|
|
|
27
345
|
| 包 | 支持范围 |
|
|
28
346
|
| --- | --- |
|
|
@@ -33,13 +351,13 @@ pnpm 与 Yarn 用法相同(`pnpm add` / `yarn add`)。
|
|
|
33
351
|
| `@react-three/drei` | `^10.0.0` |
|
|
34
352
|
| `@react-three/xr` | `^6.6.0` |
|
|
35
353
|
|
|
36
|
-
在宿主应用中保持 React、Three.js 与 React Three Fiber 相关包的**单一副本**。重复的 `three` 树是空白画布与上下文错误(`R3F: Hooks can only be used within the Canvas component`)的常见原因。若出现该问题,请检查 `npm ls three
|
|
354
|
+
在宿主应用中保持 React、Three.js 与 React Three Fiber 相关包的**单一副本**。重复的 `three` 树是空白画布与上下文错误(`R3F: Hooks can only be used within the Canvas component`)的常见原因。若出现该问题,请检查 `npm ls three`、`pnpm why three`、`yarn why three` 或 `bun pm ls` 并去重。
|
|
37
355
|
|
|
38
356
|
### 样式表(HTML 叠加层)
|
|
39
357
|
|
|
40
|
-
在宿主应用中导入一次随包样式表。WebGL 全景(`PanoViewer`、`Sphere`、`Tile`、3D 热点)无需该文件即可渲染。内置 **HTML**
|
|
358
|
+
在宿主应用中导入一次随包样式表。WebGL 全景(`PanoViewer`、`Sphere`、`Tile`、3D 热点)无需该文件即可渲染。内置 **HTML** 叠加层依赖该文件中带 `pano-` 前缀的类名完成布局。
|
|
41
359
|
|
|
42
|
-
这些叠加层包括视频控件、字幕、默认右键菜单、热点 tooltip
|
|
360
|
+
这些叠加层包括视频控件、字幕、默认右键菜单、热点 tooltip、供辅助技术使用的界面元素,以及 WebVR 会话 UI。
|
|
43
361
|
|
|
44
362
|
```ts
|
|
45
363
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -53,10 +371,13 @@ import "@ericchen1990/pano-view/styles.css";
|
|
|
53
371
|
|
|
54
372
|
仅当你不使用这些叠加层,或完全通过 `className`、`style` 与外观属性重设样式时,可跳过该导入。若不导入样式表,叠加层仍会挂载,但会无样式显示。
|
|
55
373
|
|
|
56
|
-
##
|
|
374
|
+
## 示例
|
|
57
375
|
|
|
58
376
|
为 PanoViewer 提供明确尺寸,并在其内部渲染一个全景源。使用内置 HTML 叠加层
|
|
59
377
|
(例如视频控件、字幕、tooltip、右键菜单或 WebVR UI)时,需导入一次样式表。
|
|
378
|
+
下面是每种源的基本用法,完整 props 见对应章节。
|
|
379
|
+
|
|
380
|
+
### 等距圆柱图像
|
|
60
381
|
|
|
61
382
|
~~~tsx
|
|
62
383
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -71,19 +392,87 @@ export function Panorama() {
|
|
|
71
392
|
}
|
|
72
393
|
~~~
|
|
73
394
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
395
|
+
`previewUrl` 须为低分辨率 2:1 图像,用于在完整图像加载期间显示预览。详见 [Sphere](#sphere)。
|
|
396
|
+
|
|
397
|
+
### 立方体瓦片
|
|
398
|
+
|
|
399
|
+
从 krpano XML 复制 `url`、`multires` 与预览路径。把 Sphere 换成 Tile 之前,请阅读
|
|
400
|
+
[迁移指南](../../docs/krpano-migration.zh-CN.md)。
|
|
401
|
+
|
|
402
|
+
~~~tsx
|
|
403
|
+
import { PanoViewer, Tile } from "@ericchen1990/pano-view";
|
|
404
|
+
|
|
405
|
+
export function TilePanorama() {
|
|
406
|
+
return (
|
|
407
|
+
<PanoViewer style={{ height: 560 }}>
|
|
408
|
+
<Tile
|
|
409
|
+
baseUrl="/panoramas/room"
|
|
410
|
+
urlTemplate="tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg"
|
|
411
|
+
multires="512,1000,2000"
|
|
412
|
+
previewUrl="preview.jpg"
|
|
413
|
+
/>
|
|
414
|
+
</PanoViewer>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
~~~
|
|
418
|
+
|
|
419
|
+
占位符与预览面顺序见 [Tile](#tile)。
|
|
420
|
+
|
|
421
|
+
### 360 视频
|
|
422
|
+
|
|
423
|
+
~~~tsx
|
|
424
|
+
import { PanoViewer, PanoVideo } from "@ericchen1990/pano-view";
|
|
425
|
+
|
|
426
|
+
export function TourVideo() {
|
|
427
|
+
return (
|
|
428
|
+
<PanoViewer style={{ height: 560 }}>
|
|
429
|
+
<PanoVideo autoPlay muted src="/tour.mp4" />
|
|
430
|
+
</PanoViewer>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
~~~
|
|
434
|
+
|
|
435
|
+
多档画质、字幕与自定义控件见 [PanoVideo](#panovideo)。
|
|
436
|
+
|
|
437
|
+
### 多场景
|
|
438
|
+
|
|
439
|
+
~~~tsx
|
|
440
|
+
import { useState } from "react";
|
|
441
|
+
import { PanoViewer, Scenes, type Scene } from "@ericchen1990/pano-view";
|
|
442
|
+
|
|
443
|
+
const scenes: Scene[] = [
|
|
444
|
+
{ id: "lobby", type: "sphere", src: "/lobby.webp", previewUrl: "/lobby-preview.webp" },
|
|
445
|
+
{ id: "roof", type: "sphere", src: "/roof.webp", previewUrl: "/roof-preview.webp" },
|
|
446
|
+
];
|
|
447
|
+
|
|
448
|
+
export function Tour() {
|
|
449
|
+
const [activeSceneId, setActiveSceneId] = useState("lobby");
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<PanoViewer style={{ height: 560 }}>
|
|
453
|
+
<Scenes
|
|
454
|
+
activeSceneId={activeSceneId}
|
|
455
|
+
onActiveSceneIdChange={setActiveSceneId}
|
|
456
|
+
scenes={scenes}
|
|
457
|
+
transition="dissolve"
|
|
458
|
+
/>
|
|
459
|
+
</PanoViewer>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
~~~
|
|
463
|
+
|
|
464
|
+
过渡预设与热点渲染见 [Scenes](#scenes)。
|
|
77
465
|
|
|
78
466
|
## 导出组件
|
|
79
467
|
|
|
80
468
|
### 查看器
|
|
81
469
|
|
|
82
|
-
- [`PanoViewer`](#panoviewer) —
|
|
470
|
+
- [`PanoViewer`](#panoviewer) — 画布容器、相机、默认控件与命令式视图 API
|
|
83
471
|
- [`Sphere`](#sphere) — 单张 2:1 等距圆柱图像
|
|
472
|
+
- [`SphereTile`](#球面多分辨率瓦片) — 多分辨率等距圆柱瓦片
|
|
84
473
|
- [`PanoVideo`](#panovideo) — 全景球面上的 2:1 等距圆柱视频
|
|
85
|
-
- [`Tile`](#tile) — krpano
|
|
86
|
-
- [`Scenes`](#scenes) —
|
|
474
|
+
- [`Tile`](#tile) — krpano 风格多分辨率立方体瓦片
|
|
475
|
+
- [`Scenes`](#scenes) — 支持受控与非受控模式的多场景切换
|
|
87
476
|
- [`BackgroundAudio`](#backgroundaudio) — 全部场景共用、或按场景切换的背景声
|
|
88
477
|
- [`PanoFilter`](#panofilter) — 对全景源应用色彩与艺术滤镜
|
|
89
478
|
|
|
@@ -123,11 +512,27 @@ export function Panorama() {
|
|
|
123
512
|
- 右键菜单辅助 — `createPanoContextMenuPresets`、`composePanoContextMenuItems` 及相关 API([`PanoContextMenu`](#panocontextmenu))
|
|
124
513
|
- 多边形 / 折线校验 — `validatePolygonVertices`、`validatePolylineVertices`、`unwrapPolygonVertices`
|
|
125
514
|
|
|
515
|
+
## DOM Overlay 与弹窗
|
|
516
|
+
|
|
517
|
+
普通 DOM 界面使用 `PanoViewer.renderOverlay`,位于 Three.js 渲染树之外。`usePanoOverlay()` 提供稳定的 Portal 容器、viewer/可见区域尺寸和全屏状态。可选 `/ui` 入口提供 `PanoDialog` 与组成组件,也可以接入应用已有的 Dialog。
|
|
518
|
+
|
|
519
|
+
独立的 **[弹窗使用与自定义样式指南](./docs/dialogs.zh-CN.md)** 包含完整示例、CSS 变量默认值与放置位置、布局组合、第三方接入、二级 Portal、生命周期、API 表和排错说明。指南随 npm 包分发。在本仓库运行 `pnpm dev`,打开 `/overlays` 可体验示例。
|
|
520
|
+
|
|
521
|
+
### 默认弹窗
|
|
522
|
+
|
|
523
|
+
从[快速开始](./docs/dialogs.zh-CN.md#quick-start)接入,再[定制默认外观](./docs/dialogs.zh-CN.md#customize-the-default-appearance)。默认外观单独导入 `@ericchen1990/pano-view/ui/styles.css`,也可不导入。
|
|
524
|
+
|
|
525
|
+
多个热点使用[场景/热点到内容的完整映射示例](./docs/dialogs.zh-CN.md#choose-content-by-hotspot):五份内容、一个共享弹窗、对应项目 JSON,以及场景内热点 ID 的处理。
|
|
526
|
+
|
|
527
|
+
### 组成组件与已有 Dialog 接入
|
|
528
|
+
|
|
529
|
+
自定义标题栏、内容区和底部操作使用[组成组件](./docs/dialogs.zh-CN.md#compose-a-different-layout);已有 Dialog 使用本地 Portal 容器与 `usePanoModal(open)` [完成接入](./docs/dialogs.zh-CN.md#use-an-existing-dialog)。全屏功能放在 viewer 全局界面,不放入弹窗内容。
|
|
530
|
+
|
|
126
531
|
## PanoViewer
|
|
127
532
|
|
|
128
533
|
`PanoViewer` 负责画布、透视相机与控件。为其容器指定明确尺寸,并在内部放置一个全景源。
|
|
129
534
|
|
|
130
|
-
|
|
535
|
+
公开 API 中的角度以度为单位。偏航(yaw)为正表示向右看,俯仰(pitch)为正表示向上看。
|
|
131
536
|
|
|
132
537
|
```tsx
|
|
133
538
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -170,13 +575,13 @@ export function ControlledExample() {
|
|
|
170
575
|
}
|
|
171
576
|
```
|
|
172
577
|
|
|
173
|
-
|
|
578
|
+
句柄通过单个 `ref` 提供视图、全屏、场景、VR、视频与背景音乐控制,供宿主构建自定义界面:
|
|
174
579
|
|
|
175
580
|
**视图** — `getView`、`setView`、`reset`、`lookTo`、`moveTo`、`zoomTo`、`lookToHotspot`。
|
|
176
581
|
|
|
177
582
|
`setView` 与 `reset` 仍立即生效。导航方法返回
|
|
178
583
|
`Promise<{ status: "completed" | "cancelled" | "not-found" }>`;新的导航或用户操作会取消前一个导航。默认时长为 700ms,缓动为
|
|
179
|
-
`easeInOutCubic`,
|
|
584
|
+
`easeInOutCubic`,yaw 默认沿最短路径变化。`lookTo` 与 `lookToHotspot`
|
|
180
585
|
未传 `fov` 时保持当前 FOV。
|
|
181
586
|
|
|
182
587
|
```tsx
|
|
@@ -194,13 +599,13 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
|
|
|
194
599
|
|
|
195
600
|
**全屏** — `enterFullscreen`、`exitFullscreen`、`toggleFullscreen`、`isFullscreen`。
|
|
196
601
|
|
|
197
|
-
**场景**(需 `<Scenes />`)— `setScene(id)`、`nextScene`、`previousScene`、`getActiveSceneId`、`getSceneIds`、`isSceneTransitioning`。`Scenes`
|
|
602
|
+
**场景**(需 `<Scenes />`)— `setScene(id)`、`nextScene`、`previousScene`、`getActiveSceneId`、`getSceneIds`、`isSceneTransitioning`。`Scenes` 支持 `defaultActiveSceneId`(非受控)或 `activeSceneId`(受控)。受控模式下命令式调用会触发 `onActiveSceneIdChange`。
|
|
198
603
|
|
|
199
604
|
**WebVR**(需 `<WebVR />`)— `enterVR`、`exitVR`、`toggleVR`、`isVRAvailable`、`isVREnabled`、`getVRMode`、`requestVRPermission`。
|
|
200
605
|
|
|
201
|
-
**视频**(需 `<PanoVideo />`)— `getVideo` 返回 `PanoVideoController`,`subscribeVideo`
|
|
606
|
+
**视频**(需 `<PanoVideo />`)— `getVideo` 返回 `PanoVideoController`,`subscribeVideo` 订阅视频控制器与播放状态变更,另有快捷方法 `playVideo`、`pauseVideo`、`toggleVideo`、`seekVideo`、`setVideoVolume`、`setVideoMuted`、`toggleVideoMuted`。
|
|
202
607
|
|
|
203
|
-
**背景音乐**(需 `<BackgroundAudio />`)— `getBackgroundAudio` 返回 `BackgroundAudioController`,`subscribeBackgroundAudio`
|
|
608
|
+
**背景音乐**(需 `<BackgroundAudio />`)— `getBackgroundAudio` 返回 `BackgroundAudioController`,`subscribeBackgroundAudio` 订阅背景音频控制器与播放状态变更,另有快捷方法 `playBackgroundAudio`、`pauseBackgroundAudio`、`toggleBackgroundAudio`、`setBackgroundAudioVolume`、`setBackgroundAudioMuted`、`toggleBackgroundAudioMuted`。`BackgroundAudio` 支持 `playing`(受控)或 `defaultPlaying`(非受控)。
|
|
204
609
|
|
|
205
610
|
### 移动端媒体激活
|
|
206
611
|
|
|
@@ -222,7 +627,7 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
|
|
|
222
627
|
|
|
223
628
|
`onActivate` 的 `media` 提供 `resumeAudio()`、`playVideo({ unmute })` 和 `playBackgroundAudio()`。背景音乐若使用受控 `playing`,仍由宿主在回调内更新该状态;`viewer.activateMedia()` 可供自定义控件复用同一流程。
|
|
224
629
|
|
|
225
|
-
依赖未挂载子组件的方法(如无 `<WebVR />` 时调 `enterVR
|
|
630
|
+
依赖未挂载子组件的方法(如无 `<WebVR />` 时调 `enterVR`)不会执行相应操作;返回值按方法约定为 `false`、`undefined`、`null` 或对应的 Promise。
|
|
226
631
|
|
|
227
632
|
### 全屏
|
|
228
633
|
|
|
@@ -411,7 +816,7 @@ function BGMExample() {
|
|
|
411
816
|
}
|
|
412
817
|
```
|
|
413
818
|
|
|
414
|
-
###
|
|
819
|
+
### 综合示例
|
|
415
820
|
|
|
416
821
|
```tsx
|
|
417
822
|
import {
|
|
@@ -447,7 +852,7 @@ function TourPlayer() {
|
|
|
447
852
|
|
|
448
853
|
两个已废弃的自动旋转方法(`startAutoRotate`、`stopAutoRotate`)继续保留以兼容。鼠标、触控与键盘输入默认启用 — 普通浏览无需再渲染控件组件。仅当需要覆盖属性时渲染 `MouseControls`、`TouchControls` 或 `KeyboardControls`(子组件会替换该通道的默认实例)。通过 `controls` 调节共享行为(`inertia`、`invert`、`bouncingLimits`、`fovSpeed`、`frictionStop`、`rotateDamping`、`zoomDamping`,以及顶层的 `rotateSpeed` / `zoomSpeed`)。将 `controls.mouse` / `touch` / `keyboard` 设为 `false` 可关闭某通道,或传入选项对象(含 `enabled`)在不挂载子组件的情况下覆盖默认值。自动旋转在渲染 `AutoRotate` 之前处于关闭状态。默认右键菜单(重置视图 / 进入全屏)也会挂载 — 见 [`PanoContextMenu`](#panocontextmenu)。
|
|
449
854
|
|
|
450
|
-
拖拽与缩放会更新目标视图,相机平滑跟随。`rotateDamping` 与 `zoomDamping` 控制跟随速度,单位为秒⁻¹(默认分别为 `14` 与 `16
|
|
855
|
+
拖拽与缩放会更新目标视图,相机平滑跟随。`rotateDamping` 与 `zoomDamping` 控制跟随速度,单位为秒⁻¹(默认分别为 `14` 与 `16`);较小的正值使相机更缓慢地接近目标视角,`0` 表示该轴不做平滑。两者必须为非负有限数。命令式 `setView()` 与 `reset()` 仍为即时生效。
|
|
451
856
|
|
|
452
857
|
画布光标默认为 `grab`,鼠标拖拽时为 `grabbing`,悬停在可交互热点上为 `pointer`,拖拽热点时为 `move`。可通过 `cursors`(`default`、`dragging`、`hotspot`、`hotspotDragging`)覆盖,或传 `cursors={false}` 保持画布光标不变。热点可设置 `cursor`,仅替换该热点的悬停光标。
|
|
453
858
|
|
|
@@ -469,7 +874,11 @@ function TourPlayer() {
|
|
|
469
874
|
|
|
470
875
|
`Sphere` 需要 2:1 等距圆柱图像。当源图像的前向需要水平调整时,使用 `yawOffset`。
|
|
471
876
|
|
|
472
|
-
`
|
|
877
|
+
`Sphere` 将完整的 `src` 图像加载为单张纹理。高分辨率图像会增加下载、解码和 GPU 纹理内存开销;超出设备纹理尺寸或内存限制时,可能加载失败或导致 WebGL 上下文丢失,内存不足时也可能导致页面崩溃。当前球面渲染在顶部、底部极点附近仍可能出现可见瑕疵。
|
|
878
|
+
|
|
879
|
+
高分辨率全景优先使用立方体瓦片 [`Tile`](#tile),通过 [@ericchen1990/pano-converter](../converter/README.zh-CN.md) 生成资源。查看器会按视野加载所需层级的瓦片,避免使用单张超大纹理。已有等距圆柱瓦片可使用 [`SphereTile`](#球面多分辨率瓦片)。
|
|
880
|
+
|
|
881
|
+
`previewUrl` 为必填项,在 `src` 加载期间显示低分辨率 2:1 图像。应提供尺寸较小的预览图,以减少首次显示的下载、解码和纹理内存开销。相对路径相对于 `src` 所在目录解析;根绝对路径与 `http(s)` URL 原样使用。对于 krpano 球面场景,将 [`<preview url>`](https://krpano.com/docu/xml/#preview) 复制到 `previewUrl`。该预览必须是等距圆柱,而非立方体条带。
|
|
473
882
|
|
|
474
883
|
```tsx
|
|
475
884
|
"use client";
|
|
@@ -494,7 +903,7 @@ export function SphereExample() {
|
|
|
494
903
|
|
|
495
904
|
默认播放栏挂载在查看器叠加层上:播放/暂停、进度、时间、音量、倍速、分辨率、字幕与全屏。当浏览器允许 `HTMLMediaElement.volume` 时,音量为悬停(或点击)垂直滑块;iOS Safari 仅暴露静音。倍速菜单列出当前元素接受的速率。字幕菜单还包含字幕字号与背景不透明度的设置面板。查看器较窄时,倍速、画质与字幕会收入「更多」菜单。传 `controls={false}` 可隐藏;传外观对象可重设默认实例样式;或将 `PanoVideoControls` 作为 `PanoViewer` 子组件以替换该实例。
|
|
496
905
|
|
|
497
|
-
字幕使用 WebVTT `tracks
|
|
906
|
+
字幕使用 WebVTT `tracks`。视频元素以透明的 1×1 像素节点挂载在查看器内,字幕提示文本(cue)通过屏幕空间叠加层渲染。`captions={false}` 会隐藏叠加层与语言菜单;对象会与默认外观合并(`color`、`background`、`fontSize`、`fontFamily`、`textShadow`、`padding`、`borderRadius`、`maxWidth`、`bottom`)。这些值作为字幕初始外观;观众仍可在运行时通过默认控制栏菜单调整字幕大小与背景。
|
|
498
907
|
|
|
499
908
|
```tsx
|
|
500
909
|
"use client";
|
|
@@ -538,11 +947,11 @@ export function VideoExample() {
|
|
|
538
947
|
}
|
|
539
948
|
```
|
|
540
949
|
|
|
541
|
-
`muted` 默认为 `true
|
|
950
|
+
`muted` 默认为 `true`,以提高自动播放的兼容性;实际播放仍受浏览器策略约束。`playsInline` 也默认开启,并写入 `playsinline`、`webkit-playsinline` 以及 X5 的 `h5-page` 属性,向 iPhone Safari、微信、QQ 和 UC 请求页内播放;实际行为取决于浏览器及其版本。画中画和远程播放保持关闭,以便该元素持续作为球面纹理。元素会以 1×1 像素挂在查看器内。非静音播放可能需要用户手势;`play()` 被拒绝时 `onPlaybackStateChange` 会收到 `"blocked"`。非同源的远程视频与 VTT 文件应设置 `crossOrigin`(通常为 `"anonymous"`)。移动端请提供 H.264 + AAC 的 MP4 备选源、poster、CORS 与 Range 支持。
|
|
542
951
|
|
|
543
952
|
## Tile
|
|
544
953
|
|
|
545
|
-
`Tile`
|
|
954
|
+
`Tile` 渲染六个朝内的立方体面,并仅加载当前视野附近的瓦片。省略 `urlTemplate` 时,相对于 `baseUrl` 的路径使用本包默认:
|
|
546
955
|
|
|
547
956
|
```text
|
|
548
957
|
tiles/%s/l%l/%v/l%l_%s_%h_%v.webp
|
|
@@ -566,7 +975,7 @@ export function TileExample() {
|
|
|
566
975
|
}
|
|
567
976
|
```
|
|
568
977
|
|
|
569
|
-
krpano
|
|
978
|
+
krpano 立方体瓦片输出不遵循该默认。从 krpano XML 读取 `url`、`multires` 与预览路径([`<cube>`](https://krpano.com/docu/xml/#image.cube)、[`<preview>`](https://krpano.com/docu/xml/#preview))并传入。若这些值缺失或与磁盘文件不匹配,瓦片会解析到错误的立方体面、层级或行列,导致画面缺失或错位。
|
|
570
979
|
|
|
571
980
|
| krpano | `Tile` / `TileScene` 属性 |
|
|
572
981
|
|---|---|
|
|
@@ -599,9 +1008,9 @@ krpano 文档中的简写语法:
|
|
|
599
1008
|
/>
|
|
600
1009
|
```
|
|
601
1010
|
|
|
602
|
-
MAKE PANO (MULTIRES) 文件夹布局常为 `tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg`,场景根目录有 `preview.jpg`。该文件名为 `%v_%h
|
|
1011
|
+
MAKE PANO (MULTIRES) 文件夹布局常为 `tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg`,场景根目录有 `preview.jpg`。该文件名为 `%v_%h`(先行后列)。本包默认瓦片模板为 `%h_%v`(先列后行)。请复制 XML 中的值,不要假设两种约定一致。
|
|
603
1012
|
|
|
604
|
-
`multires`
|
|
1013
|
+
`multires` 的第一个值为瓦片尺寸。其余值按 `l1`、`l2` 及更高层级的顺序,指定递增的立方体面边长。`previewUrl` 为必填。相对路径相对于 `baseUrl` 解析;根绝对路径与 `http(s)` URL 原样使用。典型的 krpano MAKE PANO (MULTIRES) 场景如下:
|
|
605
1014
|
|
|
606
1015
|
```tsx
|
|
607
1016
|
<Tile
|
|
@@ -613,11 +1022,11 @@ MAKE PANO (MULTIRES) 文件夹布局常为 `tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg`,
|
|
|
613
1022
|
/>
|
|
614
1023
|
```
|
|
615
1024
|
|
|
616
|
-
`previewFaceOrder`
|
|
1025
|
+
`previewFaceOrder` 必须按预览图从上到下的实际顺序列出六个面代码。默认顺序为 `l`、`f`、`r`、`b`、`u`、`d`。预览图为竖向 1×6 立方体条带。krpano 的 `thumb.jpg` 通常是独立缩略图,不能替代该预览条带。
|
|
617
1026
|
|
|
618
|
-
|
|
1027
|
+
快速旋转或缩放时,已加载瓦片保持可见;新进入视野的瓦片会使用其父级或预览作为局部回退。
|
|
619
1028
|
|
|
620
|
-
对于非 krpano XML
|
|
1029
|
+
对于非 krpano XML 的布局,可用占位符模板或解析器覆盖默认。模板与解析器均可提供相对于 `baseUrl` 的路径或绝对 URL:
|
|
621
1030
|
|
|
622
1031
|
```tsx
|
|
623
1032
|
<Tile
|
|
@@ -633,11 +1042,11 @@ MAKE PANO (MULTIRES) 文件夹布局常为 `tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg`,
|
|
|
633
1042
|
|
|
634
1043
|
`resolveTileUrl` 优先于 `urlTemplate`。
|
|
635
1044
|
|
|
636
|
-
对于 krpano
|
|
1045
|
+
对于 krpano 立方体瓦片模板,`%s` 为立方体面,`%l` 为多分辨率层级。水平瓦片索引支持等价的 `%h`、`%x`、`%u`、`%c` 占位符;垂直索引支持 `%v`、`%y`、`%w`、`%r`。在索引占位符前加零可填充:例如 `%0h` 为两位水平索引,`%00v` 为三位垂直索引。立体 `%t` 与帧 `%f` 占位符不适用于表示单一非立体立方体全景的 `Tile`。
|
|
637
1046
|
|
|
638
1047
|
## Scenes
|
|
639
1048
|
|
|
640
|
-
`Scenes`
|
|
1049
|
+
`Scenes` 支持单张等距圆柱图像、立方体瓦片与等距圆柱瓦片场景之间的切换,并支持受控与非受控模式。过渡使用 GPU 帧缓冲快照与目标场景进行混合。目标场景在隐藏状态下加载预览;瓦片场景同时加载视野附近的最低分辨率层级。目标场景首次可绘制后,当前帧缓冲成为临时 GPU 纹理,并释放旧场景的源纹理。目标随即进入 `full`:转场期间已经清晰的瓦片立刻盖住预览,尚未就绪的区域继续显示父层瓦片或预览。旧场景释放后,目标场景才开始加载细节瓦片;瓦片选择范围包含当前视野及其周边的少量预留区域。
|
|
641
1050
|
|
|
642
1051
|
```tsx
|
|
643
1052
|
import {
|
|
@@ -669,19 +1078,19 @@ const scenes: Scene[] = [
|
|
|
669
1078
|
</PanoViewer>;
|
|
670
1079
|
```
|
|
671
1080
|
|
|
672
|
-
可用的
|
|
1081
|
+
可用的 krpano 风格预设包括 `none`、`crossfade`、`zoom`、`blackout`、`whiteFlash`、`slideRightToLeft`、`slideTopToBottom`、`slideDiagonal`、`circleOpen`、`verticalOpen`、`horizontalOpen`、`ellipticZoomOpen`。网格类预设为 `pixelate`、`gridWipe`、`gridWipeUp`、`gridWipeRight`、`gridWipeDiagonal`、`gridWipeCenter`、`gridWipeChecker`、`hexDissolve`。电影感预设为 `dissolve`、`shatter`、`particles`、`glitch`、`swirl`、`clockWipe`、`ripple`、`zoomBlur`、`filmBurn`。它们均在目标全景之上混合 GPU 帧缓冲快照,不会混合两个完整 360 纹理。传 `{ preset: "crossfade", duration: 0.6 }` 可覆盖预设时长。
|
|
673
1082
|
|
|
674
|
-
`transitionWaitForDetailMs` 默认为 `0
|
|
1083
|
+
`transitionWaitForDetailMs` 默认为 `0`:目标一旦可绘制就开始转场,高分辨率细节在转场期间继续加载。正数(例如 `200`)从旧画面快照完成、目标进入 `full` 之后开始计时;当前视野的目标层级就绪后可提前转场,否则最多再等待指定毫秒。该等待与转场动画时长分开计算。等待期间完整显示旧快照,目标在其下正常绘制并上传纹理。单张图像场景的细节就绪指完整图片可绘制;立方体和球面瓦片指按视口尺寸、设备像素比、FOV 和现有内存预算选出的当前视野目标层级全部可绘制,不要求视野外瓦片。负值或非有限值按 `0` 处理。`transition="none"` 使用同一条件,满足后直接切换。新的目标、中断、卸载或 WebGL 上下文丢失会取消尚未结束的等待,避免过期的就绪通知影响下一个场景。
|
|
675
1084
|
|
|
676
|
-
`onReady`
|
|
1085
|
+
`onReady` 表示源第一次可绘制。`onSceneReady` 在首个场景可绘制,或旧画面快照完成且新场景接管时触发,并不表示当前视野的完整图像或细节瓦片已加载完成。
|
|
677
1086
|
|
|
678
|
-
|
|
1087
|
+
细节加载等待或转场进行时,全景拖拽/缩放输入被锁定,`renderHotspots` 被隐藏。新的 `activeSceneId` 会取代仍在准备中的目标。`maxTextureMemoryMb` 与 `maxConcurrentTileLoads` 作用于整个 `Scenes` 查看器,而非每个瓦片场景单独计算。
|
|
679
1088
|
|
|
680
1089
|
## PanoFilter
|
|
681
1090
|
|
|
682
|
-
在 `PanoViewer` 内渲染 `PanoFilter` 可对全景源(`Sphere`、`Tile`、`PanoVideo
|
|
1091
|
+
在 `PanoViewer` 内渲染 `PanoFilter` 可对全景源(`Sphere`、`Tile`、`SphereTile`、`PanoVideo`)调色或风格化。内置预设仅作用于全景材质;`colorMatrix` 默认仅作用于全景,设 `colorMatrixScope="all"` 可同时作用于热点纹理。tooltip、视频控件与 iframe 等 DOM 叠加层不受影响。WebXR / MobileVR 使用相同的全景着色器,因此继承相同的滤镜外观。
|
|
683
1092
|
|
|
684
|
-
`intensity` 在滤镜结果与原图之间混合(`0`–`1`,默认 `1
|
|
1093
|
+
`intensity` 在滤镜结果与原图之间混合(`0`–`1`,默认 `1`)。省略组件或设 `enabled={false}` 可关闭全部滤镜;`preset="none"` 仅关闭内置预设,已配置的 `colorMatrix` 仍会生效。
|
|
685
1094
|
|
|
686
1095
|
```tsx
|
|
687
1096
|
<PanoViewer style={{ height: 560 }}>
|
|
@@ -709,7 +1118,7 @@ const scenes: Scene[] = [
|
|
|
709
1118
|
| `cartoon` | 赛璐珞着色与描边 |
|
|
710
1119
|
| `crosshatch` | 亮度交叉排线 |
|
|
711
1120
|
|
|
712
|
-
|
|
1121
|
+
内置艺术效果使用屏幕空间边缘与世界空间颗粒,避免立方体瓦片出现滤镜接缝。启用滤镜时,场景过渡快照会捕获带滤镜的全景。
|
|
713
1122
|
|
|
714
1123
|
## AutoRotate
|
|
715
1124
|
|
|
@@ -724,13 +1133,13 @@ const scenes: Scene[] = [
|
|
|
724
1133
|
|
|
725
1134
|
## BackgroundAudio
|
|
726
1135
|
|
|
727
|
-
`BackgroundAudio` 在查看器里播放背景音乐或环境声。它不是热点:没有球面位置、没有标记、音量也不随视角变化。必须作为 `PanoViewer` 的子组件,与 `Scenes`、`Sphere` 或 `Tile` **并列**。不要写进 `Scenes` 的 `renderHotspots
|
|
1136
|
+
`BackgroundAudio` 在查看器里播放背景音乐或环境声。它不是热点:没有球面位置、没有标记、音量也不随视角变化。必须作为 `PanoViewer` 的子组件,与 `Scenes`、`Sphere` 或 `Tile` **并列**。不要写进 `Scenes` 的 `renderHotspots`:场景过渡时热点会卸载,背景音频也会停止。
|
|
728
1137
|
|
|
729
|
-
`playing`
|
|
1138
|
+
传入 `playing` 时,播放状态由宿主控制;省略时使用内部状态,初始值由 `defaultPlaying` 指定(默认 `false`)。`loop` 默认 `true`。浏览器可能阻止自动播放;此时 `onPlaybackStateChange` 收到 `"blocked"`,播放意图仍为 `true` 时会在下一次点击或按键后重试。
|
|
730
1139
|
|
|
731
|
-
|
|
1140
|
+
可使用以下两种音轨配置方式。
|
|
732
1141
|
|
|
733
|
-
**全部场景共用一条音轨** — 只传 `src`,不要传 `sources
|
|
1142
|
+
**全部场景共用一条音轨** — 只传 `src`,不要传 `sources`。切换场景时音频继续播放,不会从头播放。
|
|
734
1143
|
|
|
735
1144
|
```tsx
|
|
736
1145
|
import { BackgroundAudio, PanoViewer, Scenes } from "@ericchen1990/pano-view";
|
|
@@ -741,7 +1150,7 @@ import { BackgroundAudio, PanoViewer, Scenes } from "@ericchen1990/pano-view";
|
|
|
741
1150
|
</PanoViewer>;
|
|
742
1151
|
```
|
|
743
1152
|
|
|
744
|
-
**每个场景一条音轨** — 传 `sources`(键为 `Scene.id`)和当前的 `sceneId`。省略 `sceneId` 会抛错。切换场景时,若文件变了会按 `fadeMs` 交叉淡化(默认 400 毫秒;`0`
|
|
1153
|
+
**每个场景一条音轨** — 传 `sources`(键为 `Scene.id`)和当前的 `sceneId`。省略 `sceneId` 会抛错。切换场景时,若文件变了会按 `fadeMs` 交叉淡化(默认 400 毫秒;`0` 表示立即切换);若两个场景指向同一文件则连续播放,不重新开始。
|
|
745
1154
|
|
|
746
1155
|
```tsx
|
|
747
1156
|
<PanoViewer style={{ height: 560 }}>
|
|
@@ -768,9 +1177,9 @@ import { BackgroundAudio, PanoViewer, Scenes } from "@ericchen1990/pano-view";
|
|
|
768
1177
|
/>;
|
|
769
1178
|
```
|
|
770
1179
|
|
|
771
|
-
上例中:`lobby`
|
|
1180
|
+
上例中:`lobby` 播放庭院环境声,`terrace` 静音,其余场景播放 `default.mp3`。
|
|
772
1181
|
|
|
773
|
-
|
|
1182
|
+
需要将声源定位在全景某一点,并使立体声像随视角变化时,使用 `AudioHotspot`。
|
|
774
1183
|
|
|
775
1184
|
## Gyro
|
|
776
1185
|
|
|
@@ -820,7 +1229,7 @@ export function GyroExample() {
|
|
|
820
1229
|
}
|
|
821
1230
|
```
|
|
822
1231
|
|
|
823
|
-
`camroll`(默认 `true
|
|
1232
|
+
`camroll`(默认 `true`)根据设备的翻滚角(roll)调整相机倾斜。`friction` 接受 `0` 到 `0.99`;越高越平滑但延迟更大。`softstart` 为启用时的混合时长(秒,默认 `0.5`)。`desktopSupport` 默认为 `false`,因桌面浏览器可能暴露 API 却无物理传感器。
|
|
824
1233
|
|
|
825
1234
|
`touchMode` 控制触控拖拽与设备运动的组合方式:
|
|
826
1235
|
|
|
@@ -1028,11 +1437,11 @@ import {
|
|
|
1028
1437
|
|
|
1029
1438
|
`PanoViewer` 现有的 `onViewChange` / `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` props 仍受支持,共用同一事件总线。
|
|
1030
1439
|
|
|
1031
|
-
|
|
1440
|
+
资源加载回调由各组件提供:`Sphere` 使用 `onLoad`、`onError` 和 `onPreviewError`;`Tile` / `SphereTile` 使用 `onLoadProgress`、`onTileError` 和 `onPreviewError`。各源通过 `onReady` 报告首次可绘制状态;`Scenes` 通过 `onSceneReady`、`onTransitionEnd` 和 `onTransitionError` 报告场景状态。本包不逐一实现 krpano 的 XML 加载、VR 和逐帧渲染事件。
|
|
1032
1441
|
|
|
1033
1442
|
## PanoContextMenu
|
|
1034
1443
|
|
|
1035
|
-
`PanoViewer`
|
|
1444
|
+
`PanoViewer` 会始终屏蔽浏览器原生右键菜单(包括画布、内置界面与其他叠加层),并挂载默认右键菜单。默认项:**Reset view** 与 **Enter fullscreen** / **Exit fullscreen**(文案与图标随全屏状态变化),中间有分隔线。
|
|
1036
1445
|
|
|
1037
1446
|
```tsx
|
|
1038
1447
|
<PanoViewer style={{ height: 560 }}>
|
|
@@ -1115,13 +1524,13 @@ import {
|
|
|
1115
1524
|
</PanoViewer>
|
|
1116
1525
|
```
|
|
1117
1526
|
|
|
1118
|
-
|
|
1527
|
+
使用 `{ preset: "fullscreen", label: "…" }` 可覆盖预设菜单项的显示文案。在 prop 外完全手动组装时(例如自定义 `PanoContextMenu` 子组件),调用 `createPanoContextMenuPresets({ reset, toggleFullscreen, isFullscreen })` 并使用 `.resetView` / `.fullscreen` / `.defaults`。
|
|
1119
1528
|
|
|
1120
1529
|
具体项可使用 `icon` React 节点或 `image` URL。分隔线始终来自 `"separator"` 预设。外观涵盖背景、文字色、边框、圆角、阴影、仅背景的 `opacity`(从 `MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY` / `0.4` 到 `1` 钳制;标签与图标保持不透明)、悬停/禁用色与图标尺寸。若需半透明填充而不使用 `opacity`,仍可传 `background: "rgba(...)"`。
|
|
1121
1530
|
|
|
1122
1531
|
完全自定义 DOM 菜单时,设 `contextMenu={false}` 并渲染 `PanoContextMenu` 子组件。不要同时挂载默认菜单与子 `PanoContextMenu`。避免与 `controls.mouse.buttons: ["right"]` 同时使用 — 两者都会占用右键。
|
|
1123
1532
|
|
|
1124
|
-
##
|
|
1533
|
+
## 共享热点约定与可序列化定义
|
|
1125
1534
|
|
|
1126
1535
|
每个热点有 `id`,以及可选的 `visible`、`interactive`、`pointerEvents`、`renderOrder` 与语义交互回调。点热点此外还使用受控 `position`、角度 `width`/`height`、热点 `mode` 与下文缩放选项。`interactive={false}` 保持热点可见,同时让绘制工具接收其下方的全景指针事件。`pointerEvents="none"` 忽略鼠标、触控与笔,事件穿透到全景,热点仍可键盘交互。可交互热点悬停时画布光标为 `pointer`;若设置了热点 `cursor` 则使用该值。
|
|
1127
1536
|
|
|
@@ -1161,6 +1570,11 @@ import {
|
|
|
1161
1570
|
fontSize: 13,
|
|
1162
1571
|
}}
|
|
1163
1572
|
/>
|
|
1573
|
+
```
|
|
1574
|
+
|
|
1575
|
+
按悬停触发的提示框示例:
|
|
1576
|
+
|
|
1577
|
+
```tsx
|
|
1164
1578
|
<GraphicHotspot
|
|
1165
1579
|
id="marker"
|
|
1166
1580
|
ariaLabel="Signal"
|
|
@@ -1172,7 +1586,7 @@ import {
|
|
|
1172
1586
|
/>
|
|
1173
1587
|
```
|
|
1174
1588
|
|
|
1175
|
-
持久化或宿主编辑器可使用导出的判别联合类型 `HotspotDefinition
|
|
1589
|
+
持久化或宿主编辑器可使用导出的判别联合类型 `HotspotDefinition`。渲染时按 `type` 区分各变体;使用穷尽检查的 `switch` 时,应覆盖联合类型中的全部热点类别:
|
|
1176
1590
|
|
|
1177
1591
|
```ts
|
|
1178
1592
|
import type { HotspotDefinition } from "@ericchen1990/pano-view";
|
|
@@ -1261,16 +1675,16 @@ import { GraphicHotspot } from "@ericchen1990/pano-view";
|
|
|
1261
1675
|
/>
|
|
1262
1676
|
```
|
|
1263
1677
|
|
|
1264
|
-
`cornerRadius` 相对于渲染图形较短边:`0` 为直角,`0.5`
|
|
1678
|
+
`cornerRadius` 相对于渲染图形较短边:`0` 为直角,`0.5` 为最大圆角。该值为相对比例,不使用纹理像素作为单位。
|
|
1265
1679
|
|
|
1266
1680
|
`GraphicHotspot` 与 `ImageHotspot` 相同,使用 `onLoad(texture)` 与 `onError(error)`。内置 path 本地栅格化;仅接受 SVG URL 或 SVG `path` 加显式 `viewBox`,不接受任意 SVG 标记。
|
|
1267
1681
|
|
|
1268
1682
|
## 热点模式与缩放行为
|
|
1269
1683
|
|
|
1270
|
-
|
|
1684
|
+
点热点通过 `mode` 统一设置朝向与放置方式:
|
|
1271
1685
|
|
|
1272
1686
|
- `mode="surface"` 将平面附着于局部全景表面。
|
|
1273
|
-
- `mode="billboard"`
|
|
1687
|
+
- `mode="billboard"` 使平面始终朝向相机并浮于壳层前方。`distance` 是热点到相机的距离(世界单位,默认 `10`),减小该值可将热点移近相机。内置全景壳层约在 `1000` 单位;距离会自动限制在允许范围内,避免热点角点穿过壳层。
|
|
1274
1688
|
- `scaleMode="fov"`(默认)在用户放大时热点变大。`scaleMode="fixed"` 补偿 FOV 变化,屏幕尺寸接近 `referenceFov`(默认 `75`)时的大小。
|
|
1275
1689
|
- `rotation` 绕自身法线旋转(度);正值顺时针,负值逆时针。`scale` 为角向 width/height 的整体正倍率,默认 `1`。
|
|
1276
1690
|
|
|
@@ -1321,7 +1735,7 @@ import { PolygonHotspot } from "@ericchen1990/pano-view";
|
|
|
1321
1735
|
|
|
1322
1736
|
`strokeWidth` 为屏幕空间 CSS 像素,FOV 变化时视觉一致。`strokeDashSize` 与 `strokeGapSize` 同为 CSS 像素;省略或设 `strokeDashSize` 为 `0` 为实线。拖拽整体平移所有顶点,同时归一化 yaw、钳制 pitch。单顶点编辑由创作工作流提供,非运行时热点组件职责。
|
|
1323
1737
|
|
|
1324
|
-
设 `fillOpacity={0}`
|
|
1738
|
+
设 `fillOpacity={0}` 可得仅轮廓闭合多边形。填充与轮廓共用相同球面边界采样,使轮廓沿填充区域的边界绘制。
|
|
1325
1739
|
|
|
1326
1740
|
## PolylineHotspot
|
|
1327
1741
|
|
|
@@ -1404,13 +1818,15 @@ import { VideoHotspot } from "@ericchen1990/pano-view";
|
|
|
1404
1818
|
|
|
1405
1819
|
## AudioHotspot
|
|
1406
1820
|
|
|
1407
|
-
`AudioHotspot` 在球面位置播放定向声音。立体声像跟随相机朝向;`range`
|
|
1821
|
+
`AudioHotspot` 在球面位置播放定向声音。立体声像跟随相机朝向;`range` 指视线偏离声源达到该角度时,音量衰减至静音。默认 `360` 表示不因视角衰减(仅左右声像)。定点声源可使用较小值,例如 `90`。
|
|
1822
|
+
|
|
1823
|
+
音频通过 Howler.js 的 Web Audio 模式播放(Howler.js 已包含在本包构建产物中,宿主无需另行安装)。文件需完成解码后才能开始播放,可使用 MP3 格式。`src` 可以是字符串或回退 URL 数组。组件不使用 HTML5 流式音频模式,以保留立体声像控制。
|
|
1408
1824
|
|
|
1409
|
-
|
|
1825
|
+
对于 `blob:` 等没有扩展名的音频地址,可传 `format="mp3"`,或按素材顺序传格式数组。`project.json` 中的音频定义也支持可选 `format` 字段。
|
|
1410
1826
|
|
|
1411
|
-
`playing` 与 `VideoHotspot` 一样受控。`loop` 默认 `false`,`muted` 默认 `false`,`volume` 默认 `1`,`pauseWhenHidden` 默认 `true
|
|
1827
|
+
`playing` 与 `VideoHotspot` 一样受控。`loop` 默认 `false`,`muted` 默认 `false`,`volume` 默认 `1`,`pauseWhenHidden` 默认 `true`(标签页隐藏时暂停,`playing` 仍为 `true` 时,在标签页恢复可见后继续播放)。首次用户手势会解锁音频;若自动播放被阻止,会报告 `"blocked"`,并在 `playing` 仍为 true 时于下一次指针或按键后重试 `play()`。
|
|
1412
1828
|
|
|
1413
|
-
默认标记在 **停止**(扬声器)与 **播放**(扬声器 + 声波)之间切换。可用 `icon` 自定义停止图标、`playingIcon` 自定义播放图标。只设 `icon` 时两种状态共用该图。`marker={false}`
|
|
1829
|
+
默认标记在 **停止**(扬声器)与 **播放**(扬声器 + 声波)之间切换。可用 `icon` 自定义停止图标、`playingIcon` 自定义播放图标。只设 `icon` 时两种状态共用该图。`marker={false}` 隐藏标记,仍保留定点声源。整个导览共用或按场景切换的非空间背景音频请使用 [`BackgroundAudio`](#backgroundaudio)。
|
|
1414
1830
|
|
|
1415
1831
|
```tsx
|
|
1416
1832
|
import { AudioHotspot } from "@ericchen1990/pano-view";
|
|
@@ -1486,7 +1902,7 @@ import { IframeHotspot } from "@ericchen1990/pano-view";
|
|
|
1486
1902
|
|
|
1487
1903
|
## Next.js 与 SSR
|
|
1488
1904
|
|
|
1489
|
-
|
|
1905
|
+
组件入口标记为客户端模块,应在 Client Component 边界内渲染。服务端加载配置时使用 `@ericchen1990/pano-view/data`。全景资源由浏览器加载;查看器本身无需 API 密钥,资源服务器仍需配置正确的访问权限与 CORS。
|
|
1490
1906
|
|
|
1491
1907
|
在 Client Component 或全局 CSS 中导入一次样式表:
|
|
1492
1908
|
|
|
@@ -1498,7 +1914,11 @@ import "@ericchen1990/pano-view/styles.css";
|
|
|
1498
1914
|
|
|
1499
1915
|
## 球面多分辨率瓦片
|
|
1500
1916
|
|
|
1501
|
-
`SphereTile`
|
|
1917
|
+
`SphereTile` 渲染覆盖完整 2:1 等距圆柱全景的多分辨率瓦片金字塔。组件根据视角、缩放和纹理预算选择瓦片,将每块瓦片作为独立纹理映射到对应的球面区域;尚未加载的区域使用较低分辨率瓦片或预览图显示。
|
|
1918
|
+
|
|
1919
|
+
这一方式无需将整个全景加载为单张高分辨率纹理,可降低大图解码和纹理上传的峰值开销;实际内存占用仍取决于瓦片尺寸、可见层级和缓存预算。当前球面渲染在顶部、底部极点附近仍可能出现可见瑕疵。新生成的全景资源优先采用立方体瓦片 [`Tile`](#tile);已有等距圆柱瓦片可使用 `SphereTile`。
|
|
1920
|
+
|
|
1921
|
+
基本用法:
|
|
1502
1922
|
|
|
1503
1923
|
```tsx
|
|
1504
1924
|
import { PanoViewer, SphereTile } from "@ericchen1990/pano-view";
|
|
@@ -1558,8 +1978,8 @@ const scenes: Scene[] = [
|
|
|
1558
1978
|
|
|
1559
1979
|
`onSceneReady(sceneId)` 在首个场景可绘制、或旧画面截图完成且新场景接管时触发。
|
|
1560
1980
|
此时过渡可能仍在进行,可在该回调中设置新场景视角而不改变旧画面快照。
|
|
1561
|
-
|
|
1562
|
-
|
|
1981
|
+
高分辨率细节在这次交接之后加载,转场期间也会继续加载。`transitionWaitForDetailMs`
|
|
1982
|
+
可以在设定时限内保留旧快照,等待当前视野的目标细节层级就绪,但不会改变 `onSceneReady` 的触发时机。
|
|
1563
1983
|
`onTransitionEnd` 表示混合效果播放完成。
|
|
1564
1984
|
|
|
1565
1985
|
`SphereTileScene` 保留 `SphereTile` 的资源选项,包括 `indexBase`、`resolveTileUrl`
|
|
@@ -1572,11 +1992,103 @@ const scenes: Scene[] = [
|
|
|
1572
1992
|
|
|
1573
1993
|
`PanoFilter.colorMatrix` 接收只读的行优先 4×5 RGBA 矩阵(20 个有限数值,与 SVG
|
|
1574
1994
|
`feColorMatrix` 一致),在预设滤镜之后、显示/sRGB 颜色空间中应用。`intensity`
|
|
1575
|
-
|
|
1995
|
+
控制各效果与其输入颜色的混合比例,`enabled={false}` 关闭预设和矩阵。`colorMatrixScope="all"`
|
|
1576
1996
|
将矩阵同时应用于热点纹理;默认 `"panorama"` 保持原有行为。艺术预设始终仅作用于
|
|
1577
1997
|
全景。HTML 标签、iframe 和其他 DOM 界面仍由宿主设置样式。
|
|
1578
1998
|
|
|
1579
|
-
热点的 `tooltipAppearance`
|
|
1580
|
-
|
|
1999
|
+
热点的 `tooltipAppearance` 支持 `fontFamily`、`lineHeight`、`letterSpacing`,
|
|
2000
|
+
配合 `fontSize` 使用。数字 `lineHeight` 为无单位倍数,数字 `letterSpacing` 为 CSS 像素。
|
|
1581
2001
|
|
|
1582
2002
|
回归验证:`pnpm --filter @ericchen1990/pano-view test`(Node 22+)。
|
|
2003
|
+
|
|
2004
|
+
## 常见问题与故障排查
|
|
2005
|
+
|
|
2006
|
+
### 为什么推荐立方体瓦片?
|
|
2007
|
+
|
|
2008
|
+
[`Tile`](#tile) 根据视野和缩放加载立方体瓦片,可避免将完整全景作为单张超大纹理加载,并避开当前球面渲染在极点附近的瑕疵。已有等距圆柱瓦片可使用 [`SphereTile`](#球面多分辨率瓦片),同样支持按视野加载;瓦片化本身不会消除球面极点附近的渲染瑕疵。`Sphere` 适合尺寸可控的单张图像。预览图用于改善首次显示速度,仍应控制其尺寸。详见 [Sphere](#sphere)。
|
|
2009
|
+
|
|
2010
|
+
### 没有数据库,怎样发布多个场景?
|
|
2011
|
+
|
|
2012
|
+
保留转换目录结构,编写引用各 `viewer.json` 的 `project.json`,用 `loadPanoProject` 和 `ProjectScenes` 展示。将 React 构建产物和资源上传到静态托管即可。部署到 `/demo/` 等子目录时,项目入口也要带正确前缀;项目内部优先用相对地址。
|
|
2013
|
+
|
|
2014
|
+
### JSON 或纹理跨域加载失败?
|
|
2015
|
+
|
|
2016
|
+
先看浏览器 Network 与 Console,确认具体失败的是配置、预览还是瓦片。服务端/CDN 必须允许前端 origin 读取这些资源。公开资源可以使用 `Access-Control-Allow-Origin: *`;携带 Cookie 等凭据时必须使用明确允许的 origin 和 `Access-Control-Allow-Credentials: true`,动态响应 origin 时设置 `Vary: Origin`。浏览器可能只向 JavaScript 暴露 `Failed to fetch`,库不会据此断言原因。
|
|
2017
|
+
|
|
2018
|
+
`no-cors` 得到不可读取的 opaque 响应,不能解析 JSON,也不能用于需要 CORS 的 WebGL 纹理。应修改服务器配置,不能靠前端关闭安全检查。详见 [MDN CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) 和 [WebGL 纹理](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL)。
|
|
2019
|
+
|
|
2020
|
+
S3 控制台的公开读取 CORS 示例(替换成实际网站 origin):
|
|
2021
|
+
|
|
2022
|
+
```json
|
|
2023
|
+
[{
|
|
2024
|
+
"AllowedOrigins": ["https://app.example.com"],
|
|
2025
|
+
"AllowedMethods": ["GET", "HEAD"],
|
|
2026
|
+
"AllowedHeaders": ["Authorization"]
|
|
2027
|
+
}]
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
CORS 不授予对象读取权限。使用 CDN 时也需确保实际响应带有正确头。参阅 [S3 CORS 官方说明](https://docs.aws.amazon.com/AmazonS3/latest/userguide/cors.html)。
|
|
2031
|
+
|
|
2032
|
+
### 404 或请求地址看起来不对?
|
|
2033
|
+
|
|
2034
|
+
比较 Network 中的完整 URL 与云端对象 key,检查 `baseUrl`、多一层目录、大小写及文件名。只换域名或前缀时调整根地址;内部文件也改名时使用资源映射。保留转换器的模板,尤其注意 `%v_%h` 与 `%h_%v` 的区别。
|
|
2035
|
+
|
|
2036
|
+
### 报 INVALID_JSON,响应却是 200?
|
|
2037
|
+
|
|
2038
|
+
打开响应正文检查是否为 HTML。常见原因是 SPA fallback 把不存在的 JSON 路径返回为首页,或认证系统重定向到登录页。修正配置地址和静态路由;HTTP 200 不代表内容一定是 JSON。
|
|
2039
|
+
|
|
2040
|
+
### JSON 正常,图片却是 401/403 或签名过期?
|
|
2041
|
+
|
|
2042
|
+
配置认证与图片读取权限分别处理。`requestInit` 的头和凭据只用于 JSON;配置 URL 的 token 不会自动附加到瓦片。为图片提供可用的公开 URL、签名 URL 或适合应用的认证访问方案。异步取得签名后再加载/映射,签名过期后重新获取;不要把临时凭据永久写入转换 JSON。
|
|
2043
|
+
|
|
2044
|
+
### 配置加载成功,为什么画面仍是空白?
|
|
2045
|
+
|
|
2046
|
+
检查 `PanoViewer` 的容器是否有非零高度、纹理是否成功,以及 Console 中的 WebGL 错误。保持 React、Three.js 和 React Three Fiber 单一副本。纯数据加载成功不代表资源可绘制,应观察 `onSceneReady` 和 `onTransitionError`。
|
|
2047
|
+
|
|
2048
|
+
### 预览正常,高清缺失或错位?
|
|
2049
|
+
|
|
2050
|
+
检查瓦片是否全部上传,使用实际层级尺寸及转换输出的 `urlTemplate`、`previewFaceOrder`、`indexBase`。不要假定层级总能乘二或混淆从零开始的逻辑地址与存储编号;自定义 resolver 需要返回原文件对应的地址。
|
|
2051
|
+
|
|
2052
|
+
### 热点不显示,或点击不跳转?
|
|
2053
|
+
|
|
2054
|
+
检查所属场景、`visible`、`interactive`、`pointerEvents`、角度与资源请求。场景过渡时热点会暂时隐藏。场景 ID 和热点 ID 按各自范围唯一,`action.sceneId` 必须存在;`preventDefault()` 会取消默认动作。未配置 action 的文本只是文字,不会自动创建弹窗。
|
|
2055
|
+
|
|
2056
|
+
### 音视频没有播放?
|
|
2057
|
+
|
|
2058
|
+
使用点击/键盘等用户手势触发播放,检查格式和权限,并用 `onHotspotError` 查看失败。浏览器可阻止非静音自动播放;隐藏的标签页和设备规则也可能影响播放。组件不会绕过浏览器限制。
|
|
2059
|
+
|
|
2060
|
+
### 双击本地 HTML、混合内容、缓存或版本问题?
|
|
2061
|
+
|
|
2062
|
+
通过本地 HTTP(S) 服务预览,不使用 `file://`。HTTPS 页面使用 HTTPS 资源。采用带版本的资源目录,并协调项目 JSON 与图片的缓存,避免旧配置请求新目录。`UNSUPPORTED_SCHEMA_VERSION` 需要匹配支持的配置版本;平面图没有 `viewer.json`,需要平面查看器。
|
|
2063
|
+
|
|
2064
|
+
转换阶段的问题另见 [converter 错误码与故障排查](../converter/README.zh-CN.md#错误码与故障排查)。
|
|
2065
|
+
|
|
2066
|
+
### 相机中心范围
|
|
2067
|
+
|
|
2068
|
+
`PanoViewer`、`Scenes` 的每个场景及 `project.json` 的每个场景均支持可选的 `viewLimits`:
|
|
2069
|
+
|
|
2070
|
+
```ts
|
|
2071
|
+
viewLimits: {
|
|
2072
|
+
yaw: { min: 170, max: -170 },
|
|
2073
|
+
pitch: { min: -30, max: 45 },
|
|
2074
|
+
fov: { min: 40, max: 90 },
|
|
2075
|
+
}
|
|
2076
|
+
```
|
|
2077
|
+
|
|
2078
|
+
角度单位为度。Yaw 上下限在 −180…180 内;min > max 表示跨接缝,−180…180 表示完整一圈。Pitch 在 −90…90、FOV 在 1…179 内,均要求 min ≤ max。相等时锁定该轴。场景逐轴覆盖播放器设置;未配置的轴继承播放器,FOV 最后回退到原有 `minFov` / `maxFov`(默认 30 / 100)。范围限制相机中心,覆盖鼠标、触摸、键盘、惯性、自动旋转、陀螺仪及视角导航 API,不保证整个可见画面不超界。显式配置的轴即使启用 `bouncingLimits` 也使用硬边界。Yaw 动画沿允许区间插值,不走穿过禁区的最短路径;沉浸式 WebXR 继续由头显控制姿态。
|
|
2079
|
+
|
|
2080
|
+
新场景接管画面时先应用范围,再设置初始视角。实时修改会取消导航与惯性、收回越界视角,不修改保存的初始视角或重新加载纹理。移除场景范围时恢复播放器设置。
|
|
2081
|
+
|
|
2082
|
+
### 热点交互事件与自定义动作
|
|
2083
|
+
|
|
2084
|
+
`events` 为 `click`、`pointerenter`、`pointerleave` 分别绑定一个动作。三个事件均支持 `scene`、`view`、`popup` 和 `{ type: "custom", actionId, params? }`。`params` 是 JSON 对象;函数、凭据、API 实现和表单定义由宿主维护。参数内的字符串不作为素材解析或打包。
|
|
2085
|
+
|
|
2086
|
+
```json
|
|
2087
|
+
{"events":{"click":{"type":"scene","sceneId":"hall"},"pointerenter":{"type":"custom","actionId":"product.preview","params":{"visible":true}},"pointerleave":{"type":"custom","actionId":"product.preview","params":{"visible":false}}}}
|
|
2088
|
+
```
|
|
2089
|
+
|
|
2090
|
+
旧 `action` 继续作为点击动作的后备,`events.click` 优先;编辑或清除点击时移除旧字段。打开项目不自动迁移。新事件配置需要升级后的播放器,项目 schemaVersion 仍为 1。没有点击动作的媒体继续切换播放,悬停没有默认动作。
|
|
2091
|
+
|
|
2092
|
+
`ProjectScenes.onHotspotEvent(event)` 提供 `trigger`、`sceneId`、`hotspot`、`interaction` 和 `preventDefault()`。点击时先调用它,再调用原有 `onHotspotClick`,任一回调均可取消动作。`onCustomAction(event)` 额外提供 `action`,支持返回 Promise。宿主按稳定的 `action.actionId` 分发,校验业务参数,对未知动作抛出错误。缺少执行器、同步异常和 Promise 拒绝进入附带 `trigger` 的 `onHotspotError`。请求、鉴权、取消、防重复和迟到响应由宿主负责;各事件独立分发,不自动重试。
|
|
2093
|
+
|
|
2094
|
+
悬停按整个热点的边界触发一次,跳过触屏和键盘焦点,并遵循可见性、指针交互设置。切换场景、隐藏和卸载仅清理状态,不执行离开动作。原有 tooltip 行为独立保留。自定义渲染器需同时转发 `context.onHoverChange(hovered, interaction)` 和 `context.onClick`;调用 `context.renderDefault()` 会自动接线。
|