@ericchen1990/pano-view 3.4.2 → 3.5.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 +522 -73
- package/README.zh-CN.md +490 -71
- package/dist/data.cjs +903 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.mts +499 -0
- package/dist/data.d.ts +499 -0
- package/dist/data.mjs +880 -0
- package/dist/data.mjs.map +1 -0
- package/dist/index.cjs +8901 -8569
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +600 -458
- package/dist/index.d.ts +600 -458
- package/dist/index.mjs +8980 -8646
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -5
package/README.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.zh-CN.md)
|
|
4
4
|
|
|
@@ -6,6 +6,256 @@ Composable React components for equirectangular and krpano-style multiresolution
|
|
|
6
6
|
|
|
7
7
|
Try the components in the [live playground](https://pano-view-playground.vercel.app/?utm_source=github&utm_medium=readme&utm_campaign=package-readme&utm_content=playground).
|
|
8
8
|
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [pano-converter](#pano-converter)
|
|
12
|
+
- [Data sources and choosing an approach](#data-sources-and-choosing-an-approach)
|
|
13
|
+
- [Static projects and JSON data](#static-projects-and-json-data)
|
|
14
|
+
- [FAQ and troubleshooting](#faq-and-troubleshooting)
|
|
15
|
+
- [krpano-compatible tile output](#krpano-compatible-tile-output)
|
|
16
|
+
- [Install](#install)
|
|
17
|
+
- [Examples](#examples)
|
|
18
|
+
- [Exported components](#exported-components)
|
|
19
|
+
- [PanoViewer](#panoviewer)
|
|
20
|
+
- [Sphere](#sphere)
|
|
21
|
+
- [PanoVideo](#panovideo)
|
|
22
|
+
- [Tile](#tile)
|
|
23
|
+
- [Scenes](#scenes)
|
|
24
|
+
- [PanoFilter](#panofilter)
|
|
25
|
+
- [AutoRotate](#autorotate)
|
|
26
|
+
- [BackgroundAudio](#backgroundaudio)
|
|
27
|
+
- [Gyro](#gyro)
|
|
28
|
+
- [WebVR](#webvr)
|
|
29
|
+
- [Mouse, touch, and keyboard controls](#mouse-touch-and-keyboard-controls)
|
|
30
|
+
- [Panorama coordinate events](#panorama-coordinate-events)
|
|
31
|
+
- [PanoEvents](#panoevents)
|
|
32
|
+
- [PanoContextMenu](#panocontextmenu)
|
|
33
|
+
- [Shared hotspot contract and saved definitions](#shared-hotspot-contract-and-saved-definitions)
|
|
34
|
+
- [ImageHotspot](#imagehotspot)
|
|
35
|
+
- [GraphicHotspot](#graphichotspot)
|
|
36
|
+
- [Hotspot mode and zoom behaviour](#hotspot-mode-and-zoom-behaviour)
|
|
37
|
+
- [PolygonHotspot](#polygonhotspot)
|
|
38
|
+
- [PolylineHotspot](#polylinehotspot)
|
|
39
|
+
- [SequenceHotspot](#sequencehotspot)
|
|
40
|
+
- [VideoHotspot](#videohotspot)
|
|
41
|
+
- [AudioHotspot](#audiohotspot)
|
|
42
|
+
- [TextHotspot](#texthotspot)
|
|
43
|
+
- [IframeHotspot](#iframehotspot)
|
|
44
|
+
- [Next.js and SSR](#nextjs-and-ssr)
|
|
45
|
+
- [Spherical multiresolution tiles](#spherical-multiresolution-tiles)
|
|
46
|
+
- [Accessibility matrices and tooltip typography](#accessibility-matrices-and-tooltip-typography)
|
|
47
|
+
|
|
48
|
+
## pano-converter
|
|
49
|
+
|
|
50
|
+
[@ericchen1990/pano-converter](../converter/README.md) is the other package in
|
|
51
|
+
this repository. It generates cube and equirectangular tile pyramids in
|
|
52
|
+
Node.js and through the `pano-convert` CLI, then returns `viewerConfig` for
|
|
53
|
+
[`Tile`](#tile), [`SphereTile`](#spherical-multiresolution-tiles), and
|
|
54
|
+
[`Scenes`](#scenes). It does not depend on React.
|
|
55
|
+
|
|
56
|
+
## Data sources and choosing an approach
|
|
57
|
+
|
|
58
|
+
The viewer accepts configuration objects or loads JSON over HTTP. It does not connect to a database: your host backend owns database connections, queries, authorization and response mapping. A database-backed API can also return JSON; the distinction is static files versus dynamically supplied configuration, not the JSON format itself. Preview images, tiles and media can use object storage/CDN with either approach.
|
|
59
|
+
|
|
60
|
+
| Consideration | Static JSON + CDN | Database + backend API |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| Configuration reads | No database queries; storage, request and traffic costs still apply | Requests that reach the backend need processing and may query the database; caching can reduce that work |
|
|
63
|
+
| Updates | Generate and publish files, then manage entry references and caches | No static-file publication is required; freshness still depends on backend and client caches |
|
|
64
|
+
| Access and personalization | Public files suit public content; private delivery needs hosting/CDN access control | Backend can authorize users/organizations and return different configurations |
|
|
65
|
+
| Availability and maintenance | Depends on static hosting/CDN; maintain the publication and cache-update process | Depends on the API and its database/cache services; maintain queries, authorization and cache invalidation |
|
|
66
|
+
| Best fit | Public, published tours; infrequent updates; high read traffic | Editing previews, private content, frequent updates and personalization |
|
|
67
|
+
|
|
68
|
+
**Prefer JSON + CDN for public, published tours; prefer database + API for editing previews, private content and personalization.** If both are needed, use the database as the editing source and generate a JSON snapshot on publication. That hybrid approach needs an application-owned publication process; the package does not generate or synchronize snapshots for you.
|
|
69
|
+
|
|
70
|
+
Neither path automatically polls or subscribes to configuration changes. To update an already-open page, the host must reload the configuration and pass the new data to the viewer. An uncached API read can return current database values, but does not make an existing viewer update automatically.
|
|
71
|
+
|
|
72
|
+
### Database-backed configuration
|
|
73
|
+
|
|
74
|
+
In your backend, query and authorize the tour, then map only the intended configuration fields into a project. The application-owned `readAuthorizedTour` below performs that database read and access check; it is not a package API. Its scene records contain the compact `viewerConfig` saved from conversion, asset directories, views and hotspot definitions:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { loadPanoProject } from "@ericchen1990/pano-view/data";
|
|
78
|
+
import { readAuthorizedTour } from "./tour-repository";
|
|
79
|
+
|
|
80
|
+
export async function loadTourFromDatabase(tourId: string) {
|
|
81
|
+
const tour = await readAuthorizedTour(tourId);
|
|
82
|
+
return loadPanoProject({
|
|
83
|
+
schemaVersion: 1,
|
|
84
|
+
initialSceneId: tour.initialSceneId,
|
|
85
|
+
scenes: tour.scenes.map((scene) => ({
|
|
86
|
+
id: scene.id,
|
|
87
|
+
title: scene.title,
|
|
88
|
+
source: {
|
|
89
|
+
viewerConfig: scene.viewerConfig,
|
|
90
|
+
baseUrl: scene.assetBaseUrl,
|
|
91
|
+
},
|
|
92
|
+
initialView: scene.initialView,
|
|
93
|
+
hotspots: scene.hotspots,
|
|
94
|
+
})),
|
|
95
|
+
}, { baseUrl: tour.assetBaseUrl });
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Object input requires `baseUrl`: here `tour.assetBaseUrl` is an absolute directory URL such as `https://cdn.example.com/tours/123/v12/`. Project-relative hotspot URLs resolve against that directory; each scene's optional `source.baseUrl` overrides its panorama tile/preview directory. With every `source.viewerConfig` inline, this call makes no project/viewer JSON requests; rendering still loads previews, tiles and media. On the server, import data helpers from `@ericchen1990/pano-view/data`.
|
|
100
|
+
|
|
101
|
+
Pass the resulting `LoadedPanoProject` to a viewer component; in Next.js, keep the viewer inside a client boundary:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
"use client";
|
|
105
|
+
|
|
106
|
+
import {
|
|
107
|
+
PanoViewer, ProjectScenes, type LoadedPanoProject,
|
|
108
|
+
} from "@ericchen1990/pano-view";
|
|
109
|
+
|
|
110
|
+
export function DatabaseTour({ project }: { project: LoadedPanoProject }) {
|
|
111
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
112
|
+
<ProjectScenes project={project} transition="dissolve" />
|
|
113
|
+
</PanoViewer>;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Alternatively, return a `PanoProjectDefinition` from an authorized JSON endpoint and use `loadPanoProject("https://api.example.com/tours/123", { baseUrl: "https://cdn.example.com/tours/123/v12/" })`; the URL need not end in `.json`. A single scene can use `sceneFromViewerConfig` with an existing config or `loadSceneFromJson` with an API returning a `schemaVersion: 1` viewer document, then render through `Scenes`. See [Static projects and JSON data](#static-projects-and-json-data) for request options and error handling. Protect private images/media separately: JSON request credentials are not forwarded to texture requests.
|
|
118
|
+
|
|
119
|
+
### Caching and publishing updates
|
|
120
|
+
|
|
121
|
+
`project.json` (scene structure, views and hotspots), each `viewer.json` (panorama rendering configuration), and previews/tiles/media are separate cached resources. Refreshing the project alone will not refresh an unchanged scene URL or an image at the same URL. Database-backed APIs can also be cached by the backend, CDN and client, so cache policy matters for both approaches.
|
|
122
|
+
|
|
123
|
+
- **Prefer versioned directories:** publish a complete release under a new path such as `/tours/123/v12/`, including its `project.json`, scene configs and assets, before switching the page or entry reference. Version changed assets too, and retain old assets while old configurations may still be in use. Immutable versioned files can use long-lived caching; a mutable page or pointer that selects the version needs short caching or revalidation. AWS also [recommends versioned file or directory names](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/UpdatingExistingObjects.html).
|
|
124
|
+
- **When overwriting the same URL:** wait for its configured cache lifetime to expire, require revalidation, or invalidate the affected CDN paths after uploading. CDN invalidation does not clear files already cached in browsers; a newly configured response header cannot retroactively replace an already-fresh cached response. Versioned paths avoid depending on those copies expiring. Query strings such as `?v=12` work only if the CDN includes them in its cache key; JSON query parameters are not inherited by referenced assets.
|
|
125
|
+
- **Choose headers for the resource:** `Cache-Control: no-cache` permits storage but requires validation before reuse; `no-store` tells caches not to store the response. A mutable public entry can use revalidation with validators such as `ETag`, while private/user-specific API responses need an explicit policy such as `private, no-store` and must not be shared between users. Configure hosting/CDN rules to honor the intended policy. See [MDN HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching) and [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control).
|
|
126
|
+
|
|
127
|
+
These headers and CDN operations belong to your hosting/backend setup. The loader's `requestInit` supports only `headers`, `credentials` and `signal`; it does not expose Fetch's `cache` option, invalidate CDNs or refresh an already-loaded project automatically.
|
|
128
|
+
|
|
129
|
+
## Static projects and JSON data
|
|
130
|
+
|
|
131
|
+
No database is required. The converter writes `manifest.json` for archiving/reconstruction and a small `viewer.json` for rendering. Your application authors `project.json` to connect scenes, views and hotspots. Do not pass the full manifest directly to `Scenes`.
|
|
132
|
+
|
|
133
|
+
Deploy this `project.json`, both panorama output directories and your React app as static files:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"schemaVersion": 1,
|
|
138
|
+
"initialSceneId": "entrance",
|
|
139
|
+
"scenes": [
|
|
140
|
+
{
|
|
141
|
+
"id": "entrance",
|
|
142
|
+
"source": { "viewerUrl": "./panoramas/entrance/viewer.json" },
|
|
143
|
+
"initialView": { "yaw": 0, "pitch": 0, "fov": 75 },
|
|
144
|
+
"hotspots": [{
|
|
145
|
+
"id": "to-hall", "type": "graphic", "label": "Enter hall",
|
|
146
|
+
"position": { "yaw": 30, "pitch": -10 },
|
|
147
|
+
"graphic": { "kind": "arrow" },
|
|
148
|
+
"action": { "type": "scene", "sceneId": "hall" }
|
|
149
|
+
}]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"id": "hall",
|
|
153
|
+
"source": { "viewerUrl": "./panoramas/hall/viewer.json" },
|
|
154
|
+
"hotspots": [{
|
|
155
|
+
"id": "caption", "type": "text",
|
|
156
|
+
"position": { "yaw": 0, "pitch": 5 }, "text": "Welcome"
|
|
157
|
+
}]
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`source` must contain exactly one of `viewerUrl` or inline `viewerConfig`; optional `baseUrl` overrides panorama assets for that scene. `title`, `initialView` and `hotspots` are optional. The first scene is the default initial scene. Scene IDs must be unique, hotspot IDs unique within a scene, and scene-action targets must exist.
|
|
164
|
+
|
|
165
|
+
`loadPanoProject` loads metadata with at most four concurrent requests and rejects the whole project if one scene fails. It does not preload every texture. Its `LoadedPanoProject` contains `initialSceneId` and `scenes`; each entry has a runtime `scene`, title, initial view and hotspots with resolved asset URLs. Pass it to `ProjectScenes`:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { useEffect, useState } from "react";
|
|
169
|
+
import {
|
|
170
|
+
PanoViewer, ProjectScenes, loadPanoProject, PanoDataError,
|
|
171
|
+
type LoadedPanoProject,
|
|
172
|
+
} from "@ericchen1990/pano-view";
|
|
173
|
+
|
|
174
|
+
export function Tour() {
|
|
175
|
+
const [project, setProject] = useState<LoadedPanoProject>();
|
|
176
|
+
const [error, setError] = useState<string>();
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
const controller = new AbortController();
|
|
179
|
+
loadPanoProject("/tour/project.json", {
|
|
180
|
+
requestInit: { signal: controller.signal },
|
|
181
|
+
}).then((result) => {
|
|
182
|
+
if (!controller.signal.aborted) setProject(result);
|
|
183
|
+
}).catch((cause: unknown) => {
|
|
184
|
+
if (!controller.signal.aborted) {
|
|
185
|
+
setError(cause instanceof PanoDataError
|
|
186
|
+
? `${cause.code}: ${cause.message}` : String(cause));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return () => controller.abort();
|
|
190
|
+
}, []);
|
|
191
|
+
if (error) return <p role="alert">{error}</p>;
|
|
192
|
+
if (!project) return <p role="status">Loading tour…</p>;
|
|
193
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
194
|
+
<ProjectScenes project={project} transition="dissolve" />
|
|
195
|
+
</PanoViewer>;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`ProjectScenes` belongs inside `PanoViewer` and reuses all nine existing hotspot types. A `scene` action navigates on click. Without an action, audio/video/sequence hotspots toggle playback. Media unmounts on leaving a scene and starts from its JSON settings on re-entry; browser autoplay restrictions still apply.
|
|
200
|
+
|
|
201
|
+
Configured `initialView` values apply at `onSceneReady`, defaulting missing fields to yaw 0, pitch 0, fov 75. With no view configured, the current view is retained. Hotspots hide during transitions. Control selection with `activeSceneId` / `onActiveSceneIdChange`. Call `event.preventDefault()` in `onHotspotClick` to cancel the default action. `onHotspotError` receives the scene, hotspot and original error (media errors may be events containing an `error` field). `renderHotspot(hotspot, context)` returns `undefined` for the default renderer or `null` to hide an item. Context includes `sceneId`, `playing`, `onClick`, `togglePlayback`, `reportError` and `renderDefault`.
|
|
202
|
+
|
|
203
|
+
For individual panoramas and existing configs use the pure data entry. Next.js viewer components need a client boundary; server-side data loading uses `/data`:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import {
|
|
207
|
+
sceneFromViewerConfig, loadSceneFromJson, loadPanoProject,
|
|
208
|
+
} from "@ericchen1990/pano-view/data";
|
|
209
|
+
|
|
210
|
+
// A JSON API and a different asset/CDN origin.
|
|
211
|
+
const scene = await loadSceneFromJson(
|
|
212
|
+
"https://api.example.com/rooms/123/viewer",
|
|
213
|
+
{ id: "room", baseUrl: "https://cdn.example.com/rooms/123/" },
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Existing Node.js conversion results need no extra JSON request.
|
|
217
|
+
const inlineScene = sceneFromViewerConfig(result.viewerConfig, {
|
|
218
|
+
id: "room", baseUrl: "https://cdn.example.com/rooms/123/",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Use source.viewerConfig instead of source.viewerUrl to inline scene data.
|
|
222
|
+
const project = await loadPanoProject({
|
|
223
|
+
schemaVersion: 1,
|
|
224
|
+
scenes: [{ id: "room", source: { viewerConfig: result.viewerConfig } }],
|
|
225
|
+
}, { baseUrl: "https://cdn.example.com/rooms/123/" });
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### URLs and upload mapping
|
|
229
|
+
|
|
230
|
+
| Reference | Default base |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| Project `viewerUrl`, hotspot assets, `source.baseUrl` | Project directory |
|
|
233
|
+
| Tiles and preview in external `viewer.json` | That JSON directory |
|
|
234
|
+
| Tiles and preview in inline config | Project directory |
|
|
235
|
+
| Panorama with `source.baseUrl` | Specified directory |
|
|
236
|
+
|
|
237
|
+
URL inputs use the final response directory after redirects. Object inputs require `baseUrl`; it can also override the project directory. Directory trailing slashes are normalized. Domains and cloud prefixes stay outside converter JSON. Browsers accept relative entry URLs; Node.js requires absolute HTTP(S) entry URLs. Full resource URLs remain intact; leading `/` paths resolve against the resource base origin.
|
|
238
|
+
|
|
239
|
+
If uploads rename files or scatter directories, provide synchronous `resolveAssetUrl(path, context)`. Context has `defaultUrl`, `kind` (`tile`, `preview`, `hotspot`), `sceneId` and optional `hotspotId`. Return a final absolute URL or site-root path; it is not joined again. `uploadedUrls` below is application-owned:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
const project = await loadPanoProject("/tour/project.json", {
|
|
243
|
+
resolveAssetUrl(path, context) {
|
|
244
|
+
// Map keys must distinguish scenes and, for hotspot assets, hotspot IDs.
|
|
245
|
+
const key = `${context.sceneId}/${context.hotspotId ?? ""}/${path}`;
|
|
246
|
+
return uploadedUrls[key] ?? context.defaultUrl;
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Hotspot mapping covers images, SVG image URLs, audio source arrays, video posters, playback icons and tooltip images; text, CSS and SVG path data remain unchanged. Acquire signed URLs ahead of time in your application and return them synchronously.
|
|
252
|
+
|
|
253
|
+
`requestInit` accepts only `headers`, `credentials` and `signal`, for JSON requests only. They are not forwarded to textures, and JSON query parameters are not inherited by assets. `parsePanoProject(unknown)` validates and copies project data independently. `sceneFromViewerConfig` accepts the unwrapped compact config; `loadSceneFromJson` accepts a versioned `viewer.json`.
|
|
254
|
+
|
|
255
|
+
`PanoDataError` exposes stable codes: `INVALID_OPTIONS`, `FETCH_FAILED`, `HTTP_ERROR`, `INVALID_JSON`, `INVALID_CONFIG`, `UNSUPPORTED_SCHEMA_VERSION`, `UNSUPPORTED_PROJECTION`, `INVALID_PROJECT`, `INVALID_REFERENCE`, `ABORTED`. HTTP errors carry `status`; project errors include `sceneId` and `field` where available. Successful loading means metadata is ready, not that textures have loaded; use `onSceneReady` for render readiness.
|
|
256
|
+
|
|
257
|
+
See the `/project` playground route and `public/fixtures/projects/tour/project.json` for a complete static example.
|
|
258
|
+
|
|
9
259
|
## krpano-compatible tile output
|
|
10
260
|
|
|
11
261
|
Use this package when you want a React-native viewer around equirectangular
|
|
@@ -21,17 +271,44 @@ not affiliated with krpano.
|
|
|
21
271
|
|
|
22
272
|
### Package
|
|
23
273
|
|
|
274
|
+
<details name="pano-view-install" open>
|
|
275
|
+
<summary>npm</summary>
|
|
276
|
+
|
|
24
277
|
```bash
|
|
25
278
|
npm install @ericchen1990/pano-view
|
|
26
279
|
```
|
|
27
280
|
|
|
28
|
-
|
|
281
|
+
</details>
|
|
282
|
+
<details name="pano-view-install">
|
|
283
|
+
<summary>pnpm</summary>
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pnpm add @ericchen1990/pano-view
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
</details>
|
|
290
|
+
<details name="pano-view-install">
|
|
291
|
+
<summary>yarn</summary>
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
yarn add @ericchen1990/pano-view
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
</details>
|
|
298
|
+
<details name="pano-view-install">
|
|
299
|
+
<summary>bun</summary>
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
bun add @ericchen1990/pano-view
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
</details>
|
|
29
306
|
|
|
30
307
|
### Peer dependencies
|
|
31
308
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
309
|
+
The **host application** must provide compatible versions of these packages.
|
|
310
|
+
Automatic peer-dependency installation depends on the package manager and its
|
|
311
|
+
settings. Install any missing peers explicitly using the ranges below.
|
|
35
312
|
|
|
36
313
|
| Package | Supported range |
|
|
37
314
|
| --- | --- |
|
|
@@ -45,16 +322,17 @@ this library. npm 7+ and pnpm install them automatically; Yarn Classic and npm
|
|
|
45
322
|
Keep a **single copy** of React, Three.js, and the React Three Fiber packages
|
|
46
323
|
in the host app. Duplicate `three` trees are a common cause of blank canvases
|
|
47
324
|
and context errors (`R3F: Hooks can only be used within the Canvas component`).
|
|
48
|
-
If that happens, check `npm ls three
|
|
325
|
+
If that happens, check `npm ls three`, `pnpm why three`, `yarn why three`, or
|
|
326
|
+
`bun pm ls` and dedupe.
|
|
49
327
|
|
|
50
328
|
### Stylesheet (HTML chrome)
|
|
51
329
|
|
|
52
330
|
Import the shipped stylesheet once in the host app. The WebGL panorama
|
|
53
331
|
(`PanoViewer`, `Sphere`, `Tile`, 3D hotspots) renders without it. Built-in
|
|
54
|
-
**HTML** overlays
|
|
332
|
+
**HTML** overlays depend on its prefixed `pano-*` classes for layout.
|
|
55
333
|
|
|
56
334
|
Those overlays include video controls, captions, the default context menu,
|
|
57
|
-
hotspot tooltips,
|
|
335
|
+
hotspot tooltips, controls for assistive technology, and WebVR session UI.
|
|
58
336
|
|
|
59
337
|
```ts
|
|
60
338
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -70,11 +348,14 @@ Skip this import only if you do not use those overlays, or if you restyle
|
|
|
70
348
|
them entirely through `className`, `style`, and appearance props. Without
|
|
71
349
|
the stylesheet, the overlays still mount, but they render unstyled.
|
|
72
350
|
|
|
73
|
-
##
|
|
351
|
+
## Examples
|
|
74
352
|
|
|
75
353
|
Give PanoViewer an explicit size and render one panorama source inside it.
|
|
76
354
|
Import the stylesheet once when you use built-in HTML overlays such as video
|
|
77
|
-
controls, captions, tooltips, the context menu, or WebVR UI.
|
|
355
|
+
controls, captions, tooltips, the context menu, or WebVR UI. Each example
|
|
356
|
+
below shows a basic setup; the linked section has the full props.
|
|
357
|
+
|
|
358
|
+
### Equirectangular image
|
|
78
359
|
|
|
79
360
|
~~~tsx
|
|
80
361
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -89,9 +370,78 @@ export function Panorama() {
|
|
|
89
370
|
}
|
|
90
371
|
~~~
|
|
91
372
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
373
|
+
`previewUrl` must be a low-resolution 2:1 image shown while the full image
|
|
374
|
+
loads. See [Sphere](#sphere).
|
|
375
|
+
|
|
376
|
+
### Cube tiles
|
|
377
|
+
|
|
378
|
+
Copy `url`, `multires`, and the preview path from krpano XML. Read the
|
|
379
|
+
[migration guide](../../docs/krpano-migration.md) before replacing Sphere with
|
|
380
|
+
Tile.
|
|
381
|
+
|
|
382
|
+
~~~tsx
|
|
383
|
+
import { PanoViewer, Tile } from "@ericchen1990/pano-view";
|
|
384
|
+
|
|
385
|
+
export function TilePanorama() {
|
|
386
|
+
return (
|
|
387
|
+
<PanoViewer style={{ height: 560 }}>
|
|
388
|
+
<Tile
|
|
389
|
+
baseUrl="/panoramas/room"
|
|
390
|
+
urlTemplate="tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg"
|
|
391
|
+
multires="512,1000,2000"
|
|
392
|
+
previewUrl="preview.jpg"
|
|
393
|
+
/>
|
|
394
|
+
</PanoViewer>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
~~~
|
|
398
|
+
|
|
399
|
+
Placeholders and preview face order are in [Tile](#tile).
|
|
400
|
+
|
|
401
|
+
### 360 video
|
|
402
|
+
|
|
403
|
+
~~~tsx
|
|
404
|
+
import { PanoViewer, PanoVideo } from "@ericchen1990/pano-view";
|
|
405
|
+
|
|
406
|
+
export function TourVideo() {
|
|
407
|
+
return (
|
|
408
|
+
<PanoViewer style={{ height: 560 }}>
|
|
409
|
+
<PanoVideo autoPlay muted src="/tour.mp4" />
|
|
410
|
+
</PanoViewer>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
~~~
|
|
414
|
+
|
|
415
|
+
Variants, captions, and custom controls are in [PanoVideo](#panovideo).
|
|
416
|
+
|
|
417
|
+
### Multiple scenes
|
|
418
|
+
|
|
419
|
+
~~~tsx
|
|
420
|
+
import { useState } from "react";
|
|
421
|
+
import { PanoViewer, Scenes, type Scene } from "@ericchen1990/pano-view";
|
|
422
|
+
|
|
423
|
+
const scenes: Scene[] = [
|
|
424
|
+
{ id: "lobby", type: "sphere", src: "/lobby.webp", previewUrl: "/lobby-preview.webp" },
|
|
425
|
+
{ id: "roof", type: "sphere", src: "/roof.webp", previewUrl: "/roof-preview.webp" },
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
export function Tour() {
|
|
429
|
+
const [activeSceneId, setActiveSceneId] = useState("lobby");
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<PanoViewer style={{ height: 560 }}>
|
|
433
|
+
<Scenes
|
|
434
|
+
activeSceneId={activeSceneId}
|
|
435
|
+
onActiveSceneIdChange={setActiveSceneId}
|
|
436
|
+
scenes={scenes}
|
|
437
|
+
transition="dissolve"
|
|
438
|
+
/>
|
|
439
|
+
</PanoViewer>
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
~~~
|
|
443
|
+
|
|
444
|
+
Presets and hotspot rendering are in [Scenes](#scenes).
|
|
95
445
|
|
|
96
446
|
## Exported components
|
|
97
447
|
|
|
@@ -99,9 +449,10 @@ guide documents the XML-to-prop mapping and the relative-path rules.
|
|
|
99
449
|
|
|
100
450
|
- [`PanoViewer`](#panoviewer) — canvas shell, camera, default controls, and imperative view API
|
|
101
451
|
- [`Sphere`](#sphere) — single 2:1 equirectangular image
|
|
452
|
+
- [`SphereTile`](#spherical-multiresolution-tiles) — multiresolution equirectangular tiles
|
|
102
453
|
- [`PanoVideo`](#panovideo) — 2:1 equirectangular video on the panorama sphere
|
|
103
454
|
- [`Tile`](#tile) — krpano-style multires cube tiles
|
|
104
|
-
- [`Scenes`](#scenes) —
|
|
455
|
+
- [`Scenes`](#scenes) — multi-scene switching in controlled or uncontrolled mode
|
|
105
456
|
- [`BackgroundAudio`](#backgroundaudio) — one shared track, or a different track per scene
|
|
106
457
|
- [`PanoFilter`](#panofilter) — color and artistic filters on the panorama source
|
|
107
458
|
|
|
@@ -145,7 +496,7 @@ guide documents the XML-to-prop mapping and the relative-path rules.
|
|
|
145
496
|
|
|
146
497
|
`PanoViewer` owns the canvas, perspective camera, and controls. Give its container an explicit size and place one panorama source inside it.
|
|
147
498
|
|
|
148
|
-
Angles
|
|
499
|
+
Angles in the public API are measured in degrees. Positive yaw looks right and positive pitch looks up.
|
|
149
500
|
|
|
150
501
|
```tsx
|
|
151
502
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -214,13 +565,13 @@ which use their spherical center. Hotspot ids must be unique within a viewer.
|
|
|
214
565
|
|
|
215
566
|
**Fullscreen** — `enterFullscreen`, `exitFullscreen`, `toggleFullscreen`, `isFullscreen`.
|
|
216
567
|
|
|
217
|
-
**Scenes** (requires `<Scenes />`) — `setScene(id)`, `nextScene`, `previousScene`, `getActiveSceneId`, `getSceneIds`, `isSceneTransitioning`. `Scenes`
|
|
568
|
+
**Scenes** (requires `<Scenes />`) — `setScene(id)`, `nextScene`, `previousScene`, `getActiveSceneId`, `getSceneIds`, `isSceneTransitioning`. `Scenes` accepts `defaultActiveSceneId` (uncontrolled) or `activeSceneId` (controlled). In controlled mode imperative calls invoke `onActiveSceneIdChange`.
|
|
218
569
|
|
|
219
570
|
**WebVR** (requires `<WebVR />`) — `enterVR`, `exitVR`, `toggleVR`, `isVRAvailable`, `isVREnabled`, `getVRMode`, `requestVRPermission`.
|
|
220
571
|
|
|
221
|
-
**Video** (requires `<PanoVideo />`) — `getVideo` returns the `PanoVideoController`, `subscribeVideo` subscribes to
|
|
572
|
+
**Video** (requires `<PanoVideo />`) — `getVideo` returns the `PanoVideoController`, `subscribeVideo` subscribes to video controller and playback-state changes, plus convenience aliases `playVideo`, `pauseVideo`, `toggleVideo`, `seekVideo`, `setVideoVolume`, `setVideoMuted`, `toggleVideoMuted`.
|
|
222
573
|
|
|
223
|
-
**Background Audio** (requires `<BackgroundAudio />`) — `getBackgroundAudio` returns the `BackgroundAudioController`, `subscribeBackgroundAudio` subscribes to
|
|
574
|
+
**Background Audio** (requires `<BackgroundAudio />`) — `getBackgroundAudio` returns the `BackgroundAudioController`, `subscribeBackgroundAudio` subscribes to background-audio controller and playback-state changes, plus convenience aliases `playBackgroundAudio`, `pauseBackgroundAudio`, `toggleBackgroundAudio`, `setBackgroundAudioVolume`, `setBackgroundAudioMuted`, `toggleBackgroundAudioMuted`. `BackgroundAudio` accepts `playing` (controlled) or `defaultPlaying` (uncontrolled).
|
|
224
575
|
|
|
225
576
|
### Mobile media activation
|
|
226
577
|
|
|
@@ -242,7 +593,7 @@ By default, when a child has an initial playback intent (`PanoVideo autoPlay`, a
|
|
|
242
593
|
|
|
243
594
|
The `media` argument provides `resumeAudio()`, `playVideo({ unmute })`, and `playBackgroundAudio()`. Controlled background audio remains host-owned: update its `playing` state from this callback. `viewer.activateMedia()` lets a custom control reuse the same path.
|
|
244
595
|
|
|
245
|
-
Methods that depend on an unmounted child component (e.g. `enterVR` without `<WebVR />`)
|
|
596
|
+
Methods that depend on an unmounted child component (e.g. `enterVR` without `<WebVR />`) perform no action and return `false`, `undefined`, `null`, or a corresponding Promise, depending on the method.
|
|
246
597
|
|
|
247
598
|
### Fullscreen
|
|
248
599
|
|
|
@@ -467,7 +818,7 @@ function TourPlayer() {
|
|
|
467
818
|
|
|
468
819
|
The two deprecated auto-rotation handle methods (`startAutoRotate`, `stopAutoRotate`) remain for compatibility. Mouse, touch, and keyboard input are enabled by default — you do not need to render control components for ordinary viewing. Render `MouseControls`, `TouchControls`, or `KeyboardControls` only to override properties (the child replaces that channel's default instance). Tune shared behaviour through `controls` (`inertia`, `invert`, `bouncingLimits`, `fovSpeed`, `frictionStop`, `rotateDamping`, `zoomDamping`, and top-level `rotateSpeed` / `zoomSpeed`). Disable a channel with `controls.mouse` / `touch` / `keyboard` set to `false`, or pass an options object (including `enabled`) to override defaults without mounting a child. Auto-rotation is off until you render `AutoRotate`. A default context menu (Reset view / Enter fullscreen) is also mounted — see [`PanoContextMenu`](#panocontextmenu).
|
|
469
820
|
|
|
470
|
-
Drag and zoom update a target view that the camera follows smoothly. `rotateDamping` and `zoomDamping` control that following speed in seconds^-1 (defaults: `14` and `16` respectively);
|
|
821
|
+
Drag and zoom update a target view that the camera follows smoothly. `rotateDamping` and `zoomDamping` control that following speed in seconds^-1 (defaults: `14` and `16` respectively); smaller positive values make the camera approach the target more gradually, while `0` disables smoothing for that axis. Both values must be non-negative finite numbers. Imperative `setView()` and `reset()` remain immediate.
|
|
471
822
|
|
|
472
823
|
The canvas cursor defaults to `grab`, becomes `grabbing` while dragging with the mouse, `pointer` over an interactive hotspot, and `move` while dragging a hotspot. Override those values with `cursors` (`default`, `dragging`, `hotspot`, `hotspotDragging`), or pass `cursors={false}` to leave the canvas cursor unchanged. A hotspot may set `cursor` to replace the hover cursor for that hotspot only.
|
|
473
824
|
|
|
@@ -489,7 +840,20 @@ The canvas cursor defaults to `grab`, becomes `grabbing` while dragging with the
|
|
|
489
840
|
|
|
490
841
|
`Sphere` expects a 2:1 equirectangular image. Use `yawOffset` when the source's forward direction needs horizontal adjustment.
|
|
491
842
|
|
|
843
|
+
`Sphere` loads the complete `src` image as one texture. High-resolution images
|
|
844
|
+
increase download, decoding, and GPU texture memory costs. Exceeding a device's
|
|
845
|
+
texture-size or memory limits can cause loading failures or WebGL context loss;
|
|
846
|
+
memory exhaustion can also crash the page. The current spherical renderer may
|
|
847
|
+
still show visible artifacts near the top and bottom poles.
|
|
848
|
+
|
|
849
|
+
Prefer cube tiles ([`Tile`](#tile)) for high-resolution panoramas, generated
|
|
850
|
+
with [@ericchen1990/pano-converter](../converter/README.md). The viewer loads
|
|
851
|
+
required tile levels by view, avoiding one oversized texture. For existing
|
|
852
|
+
equirectangular tiles, use [`SphereTile`](#spherical-multiresolution-tiles).
|
|
853
|
+
|
|
492
854
|
`previewUrl` is required and shows a low-resolution 2:1 image while `src` loads.
|
|
855
|
+
Provide a small preview to reduce download, decoding, and texture memory costs
|
|
856
|
+
for the initial display.
|
|
493
857
|
Relative paths resolve against the directory of `src`; root-absolute and
|
|
494
858
|
`http(s)` URLs are used as-is. For krpano sphere scenes, copy
|
|
495
859
|
[`<preview url>`](https://krpano.com/docu/xml/#preview) into `previewUrl`. That
|
|
@@ -530,8 +894,8 @@ Pass `controls={false}` to hide it, an appearance object to restyle the default
|
|
|
530
894
|
instance, or render `PanoVideoControls` as a `PanoViewer` child to replace that
|
|
531
895
|
instance.
|
|
532
896
|
|
|
533
|
-
Captions use WebVTT `tracks`. The video element is
|
|
534
|
-
rendered
|
|
897
|
+
Captions use WebVTT `tracks`. The video element is mounted inside the viewer as a
|
|
898
|
+
transparent 1×1-pixel node; cue text is rendered in a screen-space overlay. `captions={false}` hides the overlay and the language
|
|
535
899
|
menu; an object merges with the default appearance (`color`, `background`,
|
|
536
900
|
`fontSize`, `fontFamily`, `textShadow`, `padding`, `borderRadius`, `maxWidth`,
|
|
537
901
|
`bottom`). Those values act as the initial caption appearance; viewers can still
|
|
@@ -580,13 +944,14 @@ export function VideoExample() {
|
|
|
580
944
|
}
|
|
581
945
|
```
|
|
582
946
|
|
|
583
|
-
`muted` defaults to `true`
|
|
584
|
-
|
|
585
|
-
`webkit-playsinline`, and the X5 `h5-page` attributes,
|
|
586
|
-
the page on iPhone Safari and in WeChat, QQ, and UC.
|
|
947
|
+
`muted` defaults to `true` for better autoplay compatibility; playback
|
|
948
|
+
remains subject to browser policy. `playsInline` also defaults to true and writes `playsinline`,
|
|
949
|
+
`webkit-playsinline`, and the X5 `h5-page` attributes, to request playback
|
|
950
|
+
within the page on iPhone Safari and in WeChat, QQ, and UC. Actual behavior
|
|
951
|
+
depends on the browser and its version. Picture-in-picture and
|
|
587
952
|
remote playback stay disabled so the element can keep feeding the sphere
|
|
588
953
|
texture. The element is mounted inside the viewer at 1×1px. Unmuted playback
|
|
589
|
-
|
|
954
|
+
may require a user gesture; `onPlaybackStateChange` receives `"blocked"` when `play()` is
|
|
590
955
|
rejected. Remote videos and VTT files that are not same-origin should set
|
|
591
956
|
`crossOrigin` (usually `"anonymous"`). For mobile delivery, provide an H.264 +
|
|
592
957
|
AAC MP4 fallback, poster, CORS, and Range support.
|
|
@@ -673,12 +1038,12 @@ The first `multires` value is the tile size. Remaining values are ascending cube
|
|
|
673
1038
|
/>
|
|
674
1039
|
```
|
|
675
1040
|
|
|
676
|
-
`previewFaceOrder` must list
|
|
1041
|
+
`previewFaceOrder` must list all six face codes in the preview atlas's actual top-to-bottom order. The default is `l`, `f`, `r`, `b`, `u`, `d`. The preview image is a vertical 1×6 cubestrip. krpano `thumb.jpg` is a small thumbnail and is not this atlas.
|
|
677
1042
|
|
|
678
1043
|
During rapid rotation or zoom, loaded tiles remain visible while newly visible tiles use their parent level or the preview as a local fallback.
|
|
679
1044
|
|
|
680
1045
|
For layouts that are not krpano XML, override the default with a placeholder
|
|
681
|
-
template or a resolver. Both
|
|
1046
|
+
template or a resolver. Both accept paths relative to `baseUrl` or absolute URLs:
|
|
682
1047
|
|
|
683
1048
|
```tsx
|
|
684
1049
|
<Tile
|
|
@@ -704,14 +1069,14 @@ a single non-stereo cube panorama.
|
|
|
704
1069
|
|
|
705
1070
|
## Scenes
|
|
706
1071
|
|
|
707
|
-
`Scenes` switches
|
|
708
|
-
|
|
709
|
-
|
|
1072
|
+
`Scenes` switches between single-image equirectangular, cube-tile, and
|
|
1073
|
+
equirectangular-tile scenes in controlled or uncontrolled mode. Transitions blend
|
|
1074
|
+
a GPU framebuffer snapshot with the incoming scene. The incoming scene stays hidden while it loads its preview; tiled scenes also
|
|
1075
|
+
load the lowest-resolution tiles around the view. Once the scene can be drawn, the current framebuffer becomes a temporary GPU
|
|
710
1076
|
texture and the outgoing source textures are released. The incoming scene then switches to
|
|
711
1077
|
full detail immediately: tiles that are already sharp replace the preview during the blend,
|
|
712
|
-
and tiles that are still loading keep the coarser parent tile or the preview. The
|
|
713
|
-
|
|
714
|
-
the current view.
|
|
1078
|
+
and tiles that are still loading keep the coarser parent tile or the preview. The incoming scene starts loading detail tiles after the outgoing scene is
|
|
1079
|
+
released. Tile selection includes the current view and a small surrounding margin.
|
|
715
1080
|
|
|
716
1081
|
```tsx
|
|
717
1082
|
import {
|
|
@@ -743,7 +1108,7 @@ const scenes: Scene[] = [
|
|
|
743
1108
|
</PanoViewer>;
|
|
744
1109
|
```
|
|
745
1110
|
|
|
746
|
-
Available
|
|
1111
|
+
Available krpano-style presets are `none`, `crossfade`, `zoom`, `blackout`,
|
|
747
1112
|
`whiteFlash`, `slideRightToLeft`, `slideTopToBottom`, `slideDiagonal`,
|
|
748
1113
|
`circleOpen`, `verticalOpen`, `horizontalOpen`, and `ellipticZoomOpen`.
|
|
749
1114
|
Grid presets are `pixelate`, `gridWipe`, `gridWipeUp`, `gridWipeRight`,
|
|
@@ -768,7 +1133,7 @@ values are treated as `0`. `transition="none"` uses the same condition and then
|
|
|
768
1133
|
over directly. A new target, an interrupt, unmount, or a lost WebGL context cancels a
|
|
769
1134
|
pending wait so a stale readiness report cannot affect the next scene.
|
|
770
1135
|
|
|
771
|
-
`onReady`
|
|
1136
|
+
`onReady` means a source can be drawn for the first time. `onSceneReady` fires
|
|
772
1137
|
for the first paintable scene and when an incoming scene takes over after the outgoing
|
|
773
1138
|
snapshot. It does not mean the current view's full detail has finished loading.
|
|
774
1139
|
|
|
@@ -780,14 +1145,16 @@ apply to the whole `Scenes` viewer rather than to each tile scene.
|
|
|
780
1145
|
## PanoFilter
|
|
781
1146
|
|
|
782
1147
|
Render `PanoFilter` inside `PanoViewer` to color-grade or stylize the panorama
|
|
783
|
-
source (`Sphere`, `Tile`, and `PanoVideo`).
|
|
784
|
-
only
|
|
785
|
-
|
|
786
|
-
|
|
1148
|
+
source (`Sphere`, `Tile`, `SphereTile`, and `PanoVideo`). Built-in presets affect
|
|
1149
|
+
only panorama materials. `colorMatrix` defaults to the panorama; set
|
|
1150
|
+
`colorMatrixScope="all"` to include hotspot textures. DOM overlays such as
|
|
1151
|
+
tooltips, video controls, and iframes are unaffected. WebXR / MobileVR use the
|
|
1152
|
+
same panorama shader and inherit its filter appearance.
|
|
787
1153
|
|
|
788
1154
|
`intensity` mixes the filtered result with the original image (`0`–`1`,
|
|
789
|
-
default `1`). Omit the component
|
|
790
|
-
|
|
1155
|
+
default `1`). Omit the component or set `enabled={false}` to disable all filtering.
|
|
1156
|
+
`preset="none"` disables only the built-in preset; a configured `colorMatrix`
|
|
1157
|
+
still applies.
|
|
791
1158
|
|
|
792
1159
|
```tsx
|
|
793
1160
|
<PanoViewer style={{ height: 560 }}>
|
|
@@ -838,10 +1205,11 @@ follow the camera. Render it as a child of `PanoViewer`, **alongside** `Scenes`,
|
|
|
838
1205
|
`Sphere`, or `Tile`. Do not put it in `Scenes`' `renderHotspots` — hotspots
|
|
839
1206
|
unmount during scene transitions and would stop the audio.
|
|
840
1207
|
|
|
841
|
-
`playing` is
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1208
|
+
When `playing` is provided, the host controls playback. Otherwise the component
|
|
1209
|
+
uses internal state initialized by `defaultPlaying` (default `false`). `loop`
|
|
1210
|
+
defaults to `true`. If the browser blocks autoplay, `onPlaybackStateChange`
|
|
1211
|
+
receives `"blocked"`. The component retries `play()` after the next click or
|
|
1212
|
+
keypress while playback is still requested.
|
|
845
1213
|
|
|
846
1214
|
Pick one of the two patterns.
|
|
847
1215
|
|
|
@@ -1198,10 +1566,12 @@ Supported callbacks (krpano names in parentheses where applicable):
|
|
|
1198
1566
|
`PanoViewer`'s existing `onViewChange` / `onPanoramaClick` / `onPanoramaDoubleClick` /
|
|
1199
1567
|
`onPanoramaPointerMove` props remain supported and share the same event bus.
|
|
1200
1568
|
|
|
1201
|
-
Resource
|
|
1202
|
-
`
|
|
1203
|
-
`
|
|
1204
|
-
|
|
1569
|
+
Resource callbacks belong to each source: `Sphere` provides `onLoad`,
|
|
1570
|
+
`onError`, and `onPreviewError`; `Tile` / `SphereTile` provide `onLoadProgress`,
|
|
1571
|
+
`onTileError`, and `onPreviewError`. Sources report initial render readiness
|
|
1572
|
+
through `onReady`. Use `Scenes` callbacks `onSceneReady`, `onTransitionEnd`,
|
|
1573
|
+
and `onTransitionError` for scene state. This package does not provide a
|
|
1574
|
+
one-to-one mapping of krpano XML-loading, VR, or frame-render events.
|
|
1205
1575
|
|
|
1206
1576
|
## PanoContextMenu
|
|
1207
1577
|
|
|
@@ -1378,6 +1748,11 @@ color, border, corner radius, shadow, padding, and font size.
|
|
|
1378
1748
|
fontSize: 13,
|
|
1379
1749
|
}}
|
|
1380
1750
|
/>
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
For a hover-triggered tooltip:
|
|
1754
|
+
|
|
1755
|
+
```tsx
|
|
1381
1756
|
<GraphicHotspot
|
|
1382
1757
|
id="marker"
|
|
1383
1758
|
ariaLabel="Signal"
|
|
@@ -1390,9 +1765,8 @@ color, border, corner radius, shadow, padding, and font size.
|
|
|
1390
1765
|
```
|
|
1391
1766
|
|
|
1392
1767
|
For persistence or a host-owned editor, use the exported discriminated
|
|
1393
|
-
`HotspotDefinition` union.
|
|
1394
|
-
|
|
1395
|
-
categories:
|
|
1768
|
+
`HotspotDefinition` union. Render each variant according to `type`; an exhaustive
|
|
1769
|
+
`switch` must cover every hotspot category in the union:
|
|
1396
1770
|
|
|
1397
1771
|
```ts
|
|
1398
1772
|
import type { HotspotDefinition } from "@ericchen1990/pano-view";
|
|
@@ -1494,8 +1868,8 @@ property to orient them in another direction.
|
|
|
1494
1868
|
```
|
|
1495
1869
|
|
|
1496
1870
|
`cornerRadius` is relative to the rendered graphic's shorter side: `0` keeps
|
|
1497
|
-
square corners and `0.5` produces the maximum rounded corners.
|
|
1498
|
-
|
|
1871
|
+
square corners and `0.5` produces the maximum rounded corners. The value is a
|
|
1872
|
+
relative proportion, not a texture-pixel measurement.
|
|
1499
1873
|
|
|
1500
1874
|
`GraphicHotspot` uses the same `onLoad(texture)` and `onError(error)` callbacks
|
|
1501
1875
|
as `ImageHotspot`. Built-in paths are rasterized locally; only an SVG URL
|
|
@@ -1504,12 +1878,12 @@ markup.
|
|
|
1504
1878
|
|
|
1505
1879
|
## Hotspot mode and zoom behaviour
|
|
1506
1880
|
|
|
1507
|
-
Point hotspots use
|
|
1508
|
-
placement properties:
|
|
1881
|
+
Point hotspots use `mode` to configure orientation and placement together:
|
|
1509
1882
|
|
|
1510
1883
|
- `mode="surface"` attaches the plane to the local panorama surface.
|
|
1511
1884
|
- `mode="billboard"` keeps the plane facing the camera and floats it in front
|
|
1512
|
-
of the shell.
|
|
1885
|
+
of the shell. `distance` is the distance from the camera (world units, default `10`);
|
|
1886
|
+
decrease it to move the hotspot nearer.
|
|
1513
1887
|
The built-in panorama shell is at roughly `1000` units; distance is
|
|
1514
1888
|
automatically capped so the hotspot's corners cannot cross it.
|
|
1515
1889
|
- `scaleMode="fov"` (default) lets a hotspot grow when the user zooms in.
|
|
@@ -1576,8 +1950,7 @@ pitch clamped. Individual vertex editing is provided by the authoring
|
|
|
1576
1950
|
workflow, not the runtime hotspot component.
|
|
1577
1951
|
|
|
1578
1952
|
Set `fillOpacity={0}` for a closed outline-only polygon. The fill and outline
|
|
1579
|
-
share the same spherical edge sampling
|
|
1580
|
-
between them.
|
|
1953
|
+
share the same spherical edge sampling so the outline follows the fill boundary.
|
|
1581
1954
|
|
|
1582
1955
|
## PolylineHotspot
|
|
1583
1956
|
|
|
@@ -1688,9 +2061,9 @@ attenuation (stereo pan only). Use a smaller `range` such as `90` for a
|
|
|
1688
2061
|
spot source.
|
|
1689
2062
|
|
|
1690
2063
|
Playback uses Web Audio via Howler.js (bundled; hosts do not install
|
|
1691
|
-
`howler`). The file must decode before
|
|
1692
|
-
format. `src` may be a string or an array of fallback URLs.
|
|
1693
|
-
streaming
|
|
2064
|
+
`howler`). The file must decode before playback starts; MP3 is a suitable
|
|
2065
|
+
format. `src` may be a string or an array of fallback URLs. The component uses Web Audio rather than HTML5
|
|
2066
|
+
streaming to retain stereo panning.
|
|
1694
2067
|
|
|
1695
2068
|
`playing` is controlled like `VideoHotspot`. `loop` defaults to `false`,
|
|
1696
2069
|
`muted` to `false`, `volume` to `1`, and `pauseWhenHidden` to `true` (pause
|
|
@@ -1803,9 +2176,10 @@ not load.
|
|
|
1803
2176
|
|
|
1804
2177
|
## Next.js and SSR
|
|
1805
2178
|
|
|
1806
|
-
The
|
|
1807
|
-
Component boundary.
|
|
1808
|
-
|
|
2179
|
+
The component entry is marked as a client module; render it within a Client
|
|
2180
|
+
Component boundary. Use `@ericchen1990/pano-view/data` for server-side
|
|
2181
|
+
configuration loading. The browser loads panorama assets. The viewer itself
|
|
2182
|
+
needs no API key; asset servers still need appropriate access and CORS settings.
|
|
1809
2183
|
|
|
1810
2184
|
Import the stylesheet once from a Client Component or a global CSS file:
|
|
1811
2185
|
|
|
@@ -1817,7 +2191,20 @@ See [Stylesheet (HTML chrome)](#stylesheet-html-chrome).
|
|
|
1817
2191
|
|
|
1818
2192
|
## Spherical multiresolution tiles
|
|
1819
2193
|
|
|
1820
|
-
`SphereTile` renders
|
|
2194
|
+
`SphereTile` renders multiresolution tile pyramids covering a complete 2:1
|
|
2195
|
+
equirectangular panorama. It selects tiles according to the view, zoom, and
|
|
2196
|
+
texture budget, mapping each tile as a separate texture onto its corresponding
|
|
2197
|
+
spherical region. Coarser tiles or the preview cover regions whose detail has
|
|
2198
|
+
not loaded yet.
|
|
2199
|
+
|
|
2200
|
+
This avoids loading the entire panorama as one high-resolution texture and can
|
|
2201
|
+
reduce peak image-decoding and texture-upload costs. Actual memory use still
|
|
2202
|
+
depends on tile dimensions, visible levels, and the cache budget. The current
|
|
2203
|
+
spherical renderer may still show visible artifacts near the top and bottom
|
|
2204
|
+
poles. Prefer cube tiles ([`Tile`](#tile)) for newly generated panorama assets;
|
|
2205
|
+
use `SphereTile` for existing equirectangular tile pyramids.
|
|
2206
|
+
|
|
2207
|
+
Basic usage:
|
|
1821
2208
|
|
|
1822
2209
|
```tsx
|
|
1823
2210
|
import { PanoViewer, SphereTile } from "@ericchen1990/pano-view";
|
|
@@ -1885,9 +2272,9 @@ const scenes: Scene[] = [
|
|
|
1885
2272
|
`onSceneReady(sceneId)` fires when the first scene becomes paintable and when an
|
|
1886
2273
|
incoming scene takes over after the outgoing snapshot is captured. The transition
|
|
1887
2274
|
may still be running; use this callback to apply the incoming camera view without
|
|
1888
|
-
changing the outgoing snapshot.
|
|
1889
|
-
the blend. `transitionWaitForDetailMs` can hold the outgoing snapshot until the current
|
|
1890
|
-
view is
|
|
2275
|
+
changing the outgoing snapshot. For tiled scenes, detail loading is enabled after that handoff and continues
|
|
2276
|
+
during the blend. `transitionWaitForDetailMs` can hold the outgoing snapshot within the configured timeout until the current
|
|
2277
|
+
view's target detail is ready; it does not change when `onSceneReady` fires. `onTransitionEnd` marks
|
|
1891
2278
|
completion of the blend.
|
|
1892
2279
|
|
|
1893
2280
|
`SphereTileScene` accepts the source options of `SphereTile`, including
|
|
@@ -1902,13 +2289,75 @@ its own cache, callbacks, and loading options.
|
|
|
1902
2289
|
`PanoFilter` also accepts `colorMatrix`, a readonly row-major 4×5 RGBA matrix
|
|
1903
2290
|
(20 finite numbers, matching SVG `feColorMatrix`). The matrix is applied in
|
|
1904
2291
|
display/sRGB space after the panorama preset. `intensity` mixes each effect
|
|
1905
|
-
with
|
|
2292
|
+
with its input; `enabled={false}` disables both. Set
|
|
1906
2293
|
`colorMatrixScope="all"` to include hotspot textures; the default `"panorama"`
|
|
1907
2294
|
keeps existing behavior. Artistic presets always affect only the panorama.
|
|
1908
2295
|
HTML tooltips, iframes and other DOM chrome remain host-styled.
|
|
1909
2296
|
|
|
1910
|
-
Hotspot `tooltipAppearance`
|
|
2297
|
+
Hotspot `tooltipAppearance` accepts `fontFamily`, `lineHeight`, and
|
|
1911
2298
|
`letterSpacing`, in addition to `fontSize` and existing paint options.
|
|
1912
2299
|
`lineHeight` numbers are unitless; numeric `letterSpacing` values are CSS pixels.
|
|
1913
2300
|
|
|
1914
2301
|
Regression checks: `pnpm --filter @ericchen1990/pano-view test` (Node 22+).
|
|
2302
|
+
|
|
2303
|
+
## FAQ and troubleshooting
|
|
2304
|
+
|
|
2305
|
+
### Why prefer cube tiles?
|
|
2306
|
+
|
|
2307
|
+
[`Tile`](#tile) loads cube tiles according to the view and zoom, avoiding one oversized panorama texture and the current spherical renderer's pole artifacts. Use [`SphereTile`](#spherical-multiresolution-tiles) for existing equirectangular tiles with view-dependent loading; tiling alone does not remove spherical pole artifacts. `Sphere` suits single images with manageable dimensions. A preview improves initial display time, but its dimensions should still be kept small. See [Sphere](#sphere).
|
|
2308
|
+
|
|
2309
|
+
### How do I deploy multiple scenes without a database?
|
|
2310
|
+
|
|
2311
|
+
Keep the converter directory structure, author `project.json` referencing each `viewer.json`, and render it with `loadPanoProject` and `ProjectScenes`. Upload your React build and assets to static hosting. Under a prefix such as `/demo/`, use the correct project entry URL and prefer relative references inside the project.
|
|
2312
|
+
|
|
2313
|
+
### Why do JSON or textures fail across origins?
|
|
2314
|
+
|
|
2315
|
+
Inspect Network and Console to identify the failing config, preview or tile. The server/CDN must allow your app origin. Public anonymous resources may use `Access-Control-Allow-Origin: *`; credentialed requests need an explicit allowed origin and `Access-Control-Allow-Credentials: true`. Use `Vary: Origin` when varying by origin. JavaScript may only receive `Failed to fetch`, so the loader cannot diagnose CORS with certainty.
|
|
2316
|
+
|
|
2317
|
+
`no-cors` gives an unreadable opaque response; it cannot make JSON readable or bypass WebGL texture rules. Configure the server. See [MDN CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) and [WebGL textures](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL).
|
|
2318
|
+
|
|
2319
|
+
Example S3 console CORS configuration for reads (replace the website origin):
|
|
2320
|
+
|
|
2321
|
+
```json
|
|
2322
|
+
[{
|
|
2323
|
+
"AllowedOrigins": ["https://app.example.com"],
|
|
2324
|
+
"AllowedMethods": ["GET", "HEAD"],
|
|
2325
|
+
"AllowedHeaders": ["Authorization"]
|
|
2326
|
+
}]
|
|
2327
|
+
```
|
|
2328
|
+
|
|
2329
|
+
CORS does not grant object read permission. When using a CDN, check its actual response headers too. See the [official S3 CORS guide](https://docs.aws.amazon.com/AmazonS3/latest/userguide/cors.html).
|
|
2330
|
+
|
|
2331
|
+
### Why do tiles return 404 or use the wrong URL?
|
|
2332
|
+
|
|
2333
|
+
Compare the complete Network URL with the uploaded object key. Check `baseUrl`, extra directory levels, case and filenames. A new domain/prefix only needs a new root; renamed files need mapping. Preserve the converter template, particularly `%v_%h` versus `%h_%v`.
|
|
2334
|
+
|
|
2335
|
+
### Why INVALID_JSON with HTTP 200?
|
|
2336
|
+
|
|
2337
|
+
Inspect the response body for HTML. A static SPA fallback may serve the homepage for a missing JSON file, or authentication may redirect to a login page. Fix the URL/routing; status 200 does not guarantee JSON.
|
|
2338
|
+
|
|
2339
|
+
### Why does JSON work but images return 401/403 or expired signatures?
|
|
2340
|
+
|
|
2341
|
+
Treat config authentication and image access separately. `requestInit` headers/credentials apply to JSON only; its URL token is not appended to tiles. Supply public URLs, signed URLs, or your application's supported authentication mechanism for assets. Acquire signatures before mapping/loading and refresh them when expired; keep temporary credentials outside conversion JSON.
|
|
2342
|
+
|
|
2343
|
+
### Why is the canvas blank after config loading succeeds?
|
|
2344
|
+
|
|
2345
|
+
Ensure the viewer container has nonzero height, inspect texture requests and WebGL console errors, and keep React, Three.js and React Three Fiber singletons. Metadata success is not render readiness: observe `onSceneReady` and `onTransitionError`.
|
|
2346
|
+
|
|
2347
|
+
### Why is the preview correct but detailed tiles are missing or misplaced?
|
|
2348
|
+
|
|
2349
|
+
Upload every tile and preserve actual level sizes, `urlTemplate`, `previewFaceOrder` and `indexBase`. Do not assume all levels exactly double or confuse zero-based logical coordinates with stored indices. A custom resolver must identify the corresponding original tile.
|
|
2350
|
+
|
|
2351
|
+
### Why are hotspots missing or not navigating?
|
|
2352
|
+
|
|
2353
|
+
Check the owning scene, `visible`, `interactive`, `pointerEvents`, angles and asset requests. Hotspots hide during transitions. IDs must be unique in their respective scopes and `action.sceneId` must exist. `preventDefault()` cancels built-in actions. Text alone does not create an information dialog.
|
|
2354
|
+
|
|
2355
|
+
### Why does media not play?
|
|
2356
|
+
|
|
2357
|
+
Start playback from a click or keyboard gesture, check supported formats and permissions, and inspect `onHotspotError`. Browsers can block unmuted autoplay; tab visibility and device rules can also affect playback. The component does not bypass browser policies.
|
|
2358
|
+
|
|
2359
|
+
### What about local files, mixed content, caching and versions?
|
|
2360
|
+
|
|
2361
|
+
Serve files over local HTTP(S), rather than double-clicking `file://` HTML. HTTPS pages should load HTTPS assets. Use versioned asset directories and coordinate JSON/image caching so stale configs do not target replaced files. `UNSUPPORTED_SCHEMA_VERSION` requires a supported config version. Planar conversions have no `viewer.json` and need a planar viewer.
|
|
2362
|
+
|
|
2363
|
+
For conversion failures see [converter error codes and troubleshooting](../converter/README.md#error-codes-and-troubleshooting).
|