@ericchen1990/pano-view 3.8.0 → 3.10.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 +745 -97
- package/README.zh-CN.md +606 -56
- package/dist/data.cjs +19 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.mts +17 -1
- package/dist/data.d.ts +17 -1
- package/dist/data.mjs +19 -0
- package/dist/data.mjs.map +1 -1
- package/dist/index.cjs +2630 -819
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +218 -6
- package/dist/index.d.ts +218 -6
- package/dist/index.mjs +2533 -711
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +282 -1
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.d.mts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.mjs.map +1 -1
- package/docs/dialogs.md +51 -41
- package/docs/dialogs.zh-CN.md +51 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Try the components in the [live playground](https://pano-view-playground.vercel.
|
|
|
11
11
|
- [pano-converter](#pano-converter)
|
|
12
12
|
- [Data sources and choosing an approach](#data-sources-and-choosing-an-approach)
|
|
13
13
|
- [Static projects and JSON data](#static-projects-and-json-data)
|
|
14
|
+
- [Floor plan maps](#floor-plan-maps)
|
|
14
15
|
- [FAQ and troubleshooting](#faq-and-troubleshooting)
|
|
15
16
|
- [krpano-compatible tile output](#krpano-compatible-tile-output)
|
|
16
17
|
- [Install](#install)
|
|
@@ -22,6 +23,7 @@ Try the components in the [live playground](https://pano-view-playground.vercel.
|
|
|
22
23
|
- [PanoVideo](#panovideo)
|
|
23
24
|
- [Tile](#tile)
|
|
24
25
|
- [Scenes](#scenes)
|
|
26
|
+
- [PanoSceneMenu](#panoscenemenu)
|
|
25
27
|
- [PanoFilter](#panofilter)
|
|
26
28
|
- [AutoRotate](#autorotate)
|
|
27
29
|
- [BackgroundAudio](#backgroundaudio)
|
|
@@ -199,7 +201,9 @@ export function Tour() {
|
|
|
199
201
|
|
|
200
202
|
`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.
|
|
201
203
|
|
|
202
|
-
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
|
|
204
|
+
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`. When uncontrolled, internal state starts at `project.initialSceneId`; a new `project` reference resets that selection. Call `event.preventDefault()` in `onHotspotClick` to cancel the default action. `onHotspotError` receives `sceneId`, the hotspot, and the 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`, `onHoverChange`, `togglePlayback`, `reportError`, and `renderDefault`.
|
|
205
|
+
|
|
206
|
+
Beyond project-specific props, `ProjectScenes` accepts the `Scenes` transition and cache props: `transition`, `onSceneReady`, `transitionWaitForDetailMs`, `onTransitionEnd`, `onTransitionError`, `maxTextureMemoryMb`, `maxConcurrentTileLoads`, and `snapshotMaxPixels` (default `3686400`). It does not accept `scenes`, `defaultActiveSceneId`, or a host `renderHotspots` — the scene list comes from `project`, and hotspots are rendered by the component.
|
|
203
207
|
|
|
204
208
|
### Hotspot actions and shared popup content
|
|
205
209
|
|
|
@@ -226,24 +230,188 @@ Store this content structure in `project.json`; store media as separate files. P
|
|
|
226
230
|
|
|
227
231
|
```tsx
|
|
228
232
|
import { useState } from "react";
|
|
229
|
-
import { PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
233
|
+
import { PanoOverlay, PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
230
234
|
import { ProjectPopupDialog } from "@ericchen1990/pano-view/ui";
|
|
231
235
|
import "@ericchen1990/pano-view/ui/styles.css";
|
|
232
236
|
|
|
233
237
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
234
238
|
const [popup, setPopup] = useState<ProjectPopupDefinition | null>(null);
|
|
235
|
-
return <PanoViewer
|
|
236
|
-
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
237
|
-
)}>
|
|
239
|
+
return <PanoViewer>
|
|
238
240
|
<ProjectScenes project={project} onPopupRequest={({ popup }) => setPopup(popup)}
|
|
239
241
|
onActiveSceneIdChange={() => setPopup(null)} />
|
|
242
|
+
<PanoOverlay>
|
|
243
|
+
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
244
|
+
</PanoOverlay>
|
|
240
245
|
</PanoViewer>;
|
|
241
246
|
}
|
|
242
247
|
```
|
|
243
248
|
|
|
244
249
|
`ProjectPopupDialog` accepts `popup` (or `null` to close), `onOpenChange`, optional `closeLabel` and `onMediaError(block)`. It renders text as text, uses native audio/video controls without autoplay, and releases media when content closes or changes. It inherits `PanoDialog` fullscreen, sizing and focus behavior. Replace this component with your own overlay to customize rendering.
|
|
245
250
|
|
|
246
|
-
|
|
251
|
+
### Floor plan maps
|
|
252
|
+
|
|
253
|
+
An optional project-level `map` places scene spots on one floor image. Projects without `map` stay valid, and `schemaVersion` remains 1. `loadPanoProject` returns the map with the floor image and spot icons resolved from the project directory. `parsePanoMap` and `resolvePanoMapAssets` are also exported from `/data` for custom loaders.
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
"map": {
|
|
257
|
+
"image": "assets/map/floor.webp",
|
|
258
|
+
"interaction": "zoom",
|
|
259
|
+
"maxScale": 4,
|
|
260
|
+
"iconSize": 28,
|
|
261
|
+
"presentation": {
|
|
262
|
+
"mode": "docked",
|
|
263
|
+
"corner": "bottom-left",
|
|
264
|
+
"width": 240,
|
|
265
|
+
"height": 180,
|
|
266
|
+
"offsetX": 16,
|
|
267
|
+
"offsetY": 16
|
|
268
|
+
},
|
|
269
|
+
"controls": { "collapse": true, "zoom": true },
|
|
270
|
+
"sector": {
|
|
271
|
+
"angle": 70,
|
|
272
|
+
"radius": 56,
|
|
273
|
+
"followFov": false,
|
|
274
|
+
"color": "#38bdf8",
|
|
275
|
+
"opacity": 0.55,
|
|
276
|
+
"strokeColor": "#ffffff",
|
|
277
|
+
"strokeWidth": 1.5,
|
|
278
|
+
"strokeOpacity": 0.9
|
|
279
|
+
},
|
|
280
|
+
"spots": [{
|
|
281
|
+
"id": "entrance-spot",
|
|
282
|
+
"sceneId": "entrance",
|
|
283
|
+
"x": 0.31,
|
|
284
|
+
"y": 0.5,
|
|
285
|
+
"heading": 20,
|
|
286
|
+
"icon": "assets/map/pin.webp"
|
|
287
|
+
}]
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`image` is a project-relative path or an absolute URL. `spots` is required. Spot IDs are unique, and each `sceneId` must name a scene in the project. `x` and `y` are normalized image coordinates in [0, 1], with the origin at the top-left. `heading` is clockwise degrees from image-up when viewer yaw is 0; the drawn sector uses `heading + yaw`. Several spots may share a scene. The sector is drawn on the first spot whose `sceneId` matches the active scene. `icon` is optional; a spot without one uses the built-in marker.
|
|
292
|
+
|
|
293
|
+
`interaction` is `"fixed"` (the default) or `"zoom"`. Fixed mode fits the image in the panel and ignores the wheel, drag-pan, and zoom buttons. Zoom mode allows wheel zoom, and drag-pan once the scale is above the fitted size. Wheel events on the map do not scroll the page. `maxScale` is the maximum multiple of that fitted size, at least 1, default 8. `dragMotion` controls pan inertia and edge bounce while zoomed (both on by default). Set `false` to disable both, or `{ inertia?: boolean; bounce?: boolean }` to toggle either flag. Spot and heading edits stay direct. `controls.zoom` shows the zoom buttons only when interaction is `"zoom"`. `controls.collapse` shows expand and collapse on a docked panel. `iconSize` is the marker edge in CSS pixels, default 28. Icons and the sector keep that screen size while the floor image zooms.
|
|
294
|
+
|
|
295
|
+
`sector` is optional. `angle` is the opening in degrees, greater than 0 and less than 360. `radius` is CSS pixels and does not scale with the image. `followFov` replaces `angle` with the horizontal field of view derived from the viewer FOV and the map panel's aspect ratio. Without `gradient`, the fill is solid `color` (default `#ffffff`) at `opacity` (default 0.55). `gradient.type` is `"radial"` or `"linear"`, with at least two stops. Each stop has `offset` in [0, 1], `color`, and an optional `opacity`. Stop opacities describe only the fade shape. `sector.opacity` scales the whole fill in both solid and gradient mode. `strokeColor`, `strokeWidth`, and `strokeOpacity` draw an outline when the width is greater than 0. The outline width stays in CSS pixels.
|
|
296
|
+
|
|
297
|
+
`presentation.mode` is `"docked"` (the default) or `"dialog"`. Both accept `corner` (`top-left`, `top-right`, `bottom-left`, or `bottom-right`; default `bottom-left`), `offsetX` and `offsetY` (default 16), plus `width` and `height`. A docked panel defaults to 220×160. Dialog mode docks a launcher (48×48 when width and height are omitted) and opens the same map in a viewer-local dialog. Choosing a spot closes that dialog. The chrome is positioned on the full viewer overlay, so scrolling a parent page does not move or resize it.
|
|
298
|
+
|
|
299
|
+
The document stores saved defaults (`image`, `spots`, `sector`, `presentation`, `interaction`, `maxScale`, `dragMotion`, `controls`, `iconSize`). Mount-time props on `PanoMapOverlay` override presentation and zoom for that render without cloning the map. Priority is built-in defaults → map document → `presentationOverride` (same shape as `map.presentation`) → individual props. Spot geometry and sector styling stay in the document. `selectedSpotId` / `onSelectedSpotIdChange` are forwarded to the inner `PanoMap`. Docked collapse state is owned by the overlay; dialog mode and `editorSurface` force collapse controls off, so `collapsed` cannot be driven from outside.
|
|
300
|
+
|
|
301
|
+
#### Mounting `PanoMapOverlay`
|
|
302
|
+
|
|
303
|
+
Mount it inside `PanoOverlay` and pass the same `activeSceneId` you pass to `ProjectScenes`. Import `@ericchen1990/pano-view/styles.css` for the built-in `pano-map-*` classes. The overlay reads yaw and FOV from the viewer. It does not change scenes by itself: set the active scene from `onSelectScene`. Pass `view` when the camera state comes from outside the viewer; outside the viewer with no `view`, the surface stays at yaw `0`, pitch `0`, fov `75`. `PanoMap` is the surface alone, for a host-owned frame.
|
|
304
|
+
|
|
305
|
+
`PanoMap` also accepts controlled selection and collapse: `selectedSpotId` / `onSelectedSpotIdChange` (internal selection defaults to `null`); `collapsed` / `onCollapsedChange` (uncontrolled starts expanded); `renderChrome` to draw host controls above the surface. `showZoomControls` / `showCollapseControl` work on `PanoMap` the same way as on the overlay. `className` / `style` attach to the surface root.
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
import { useState } from "react";
|
|
309
|
+
import {
|
|
310
|
+
PanoOverlay, PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
311
|
+
} from "@ericchen1990/pano-view";
|
|
312
|
+
import "@ericchen1990/pano-view/styles.css";
|
|
313
|
+
|
|
314
|
+
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
315
|
+
const [activeSceneId, setActiveSceneId] = useState(project.initialSceneId);
|
|
316
|
+
const map = project.map;
|
|
317
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
318
|
+
<ProjectScenes
|
|
319
|
+
project={project}
|
|
320
|
+
activeSceneId={activeSceneId}
|
|
321
|
+
onActiveSceneIdChange={setActiveSceneId}
|
|
322
|
+
/>
|
|
323
|
+
{map ? (
|
|
324
|
+
<PanoOverlay>
|
|
325
|
+
<PanoMapOverlay
|
|
326
|
+
map={map}
|
|
327
|
+
activeSceneId={activeSceneId}
|
|
328
|
+
onSelectScene={setActiveSceneId}
|
|
329
|
+
/>
|
|
330
|
+
</PanoOverlay>
|
|
331
|
+
) : null}
|
|
332
|
+
</PanoViewer>;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Size, corner, and inset
|
|
337
|
+
|
|
338
|
+
- `presentation` — `"docked"` | `"dialog"` (defaults from the document, otherwise `"docked"`).
|
|
339
|
+
- `corner` — `"top-left"` | `"top-right"` | `"bottom-left"` | `"bottom-right"` (default `"bottom-left"`).
|
|
340
|
+
- `width` / `height` — docked panel size, or the dialog launcher size, in CSS pixels. Docked defaults to 220×160; the dialog launcher defaults to 48×48.
|
|
341
|
+
- `offsetX` / `offsetY` — inset from the chosen corner in CSS pixels (default 16).
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
<PanoMapOverlay
|
|
345
|
+
map={map}
|
|
346
|
+
activeSceneId={activeSceneId}
|
|
347
|
+
onSelectScene={setActiveSceneId}
|
|
348
|
+
presentation="docked"
|
|
349
|
+
corner="bottom-right"
|
|
350
|
+
width={280}
|
|
351
|
+
height={200}
|
|
352
|
+
offsetX={20}
|
|
353
|
+
offsetY={20}
|
|
354
|
+
/>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Dialog mode
|
|
358
|
+
|
|
359
|
+
Dialog mode docks a launcher button and opens the map in a viewer-local modal. Launcher size uses `width` / `height` (and `corner` / offsets). The dialog body uses a separate pair:
|
|
360
|
+
|
|
361
|
+
- `dialogWidth` / `dialogHeight` — preferred dialog size in CSS pixels (default 720×520). Both are clamped to the viewer with a 16 px margin on each side.
|
|
362
|
+
- `openLabel` / `closeLabel` / `title` — launcher accessible name, close button label, and dialog heading (defaults `"Map"`, `"Close"`, `"Map"`).
|
|
363
|
+
|
|
364
|
+
Choosing a spot closes the dialog.
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
<PanoMapOverlay
|
|
368
|
+
map={map}
|
|
369
|
+
activeSceneId={activeSceneId}
|
|
370
|
+
onSelectScene={setActiveSceneId}
|
|
371
|
+
presentation="dialog"
|
|
372
|
+
corner="bottom-left"
|
|
373
|
+
width={52}
|
|
374
|
+
height={52}
|
|
375
|
+
dialogWidth={640}
|
|
376
|
+
dialogHeight={480}
|
|
377
|
+
openLabel="Floor plan"
|
|
378
|
+
title="Floor plan"
|
|
379
|
+
closeLabel="Close"
|
|
380
|
+
/>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### Zoom and controls
|
|
384
|
+
|
|
385
|
+
These props override the map document for the current mount:
|
|
386
|
+
|
|
387
|
+
- `interaction` — `"fixed"` | `"zoom"`.
|
|
388
|
+
- `maxScale` — maximum multiple of the contain-fit size; values below 1 are treated as 1.
|
|
389
|
+
- `dragMotion` — pan inertia / edge bounce when `interaction` is `"zoom"`. Omit for both on; `false` disables both.
|
|
390
|
+
- `initialScale` — opening zoom relative to the contain-fit size, clamped to `[1, maxScale]`. View state only; not written back to the document. Applies after the image is measured and recenters. Resets only when `initialScale` or `interaction` changes; later wheel and drag keep the user's view. Shrinking `maxScale` only clamps the current scale.
|
|
391
|
+
- `showZoomControls` / `showCollapseControl` — force zoom or collapse chrome on or off.
|
|
392
|
+
- `iconSize` — marker edge in CSS pixels.
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
<PanoMapOverlay
|
|
396
|
+
map={map}
|
|
397
|
+
activeSceneId={activeSceneId}
|
|
398
|
+
onSelectScene={setActiveSceneId}
|
|
399
|
+
interaction="zoom"
|
|
400
|
+
maxScale={4}
|
|
401
|
+
initialScale={1.5}
|
|
402
|
+
showZoomControls
|
|
403
|
+
showCollapseControl
|
|
404
|
+
iconSize={32}
|
|
405
|
+
/>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### Edit mode
|
|
409
|
+
|
|
410
|
+
`mode="edit"` is the authoring surface: with `placementSceneId` set, a click adds a spot whose heading is the negation of the current yaw, so the sector points image-up at that yaw; dragging a spot moves it, and dragging the sector sets `heading`. `onEditChange` reports `{ spots?, selectedSpotId? }`. Visitor playback stays on the default `mode="view"`. `editorSurface` enlarges the panel for authoring; pass `width` / `height` to size that surface, otherwise it scales with the viewer.
|
|
411
|
+
|
|
412
|
+
Run `pnpm dev` and open `/map` for the playground. Authoring the same document is described in the [editor floor-plan section](../editor/README.md#floor-plan).
|
|
413
|
+
|
|
414
|
+
The schema remains version 1 and old projects remain valid. Projects using new actions or a floor-plan map require a viewer that supports them. `parsePanoProject(input, { draft: true })` is for authoring only: it permits empty scene lists, unfinished popup titles/media, and an empty floor-plan image. Loading, saving and package export validate strictly. Host applications may save the same document in a database or map it to tables; the viewer has no database connection.
|
|
247
415
|
|
|
248
416
|
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`:
|
|
249
417
|
|
|
@@ -274,14 +442,14 @@ const project = await loadPanoProject({
|
|
|
274
442
|
|
|
275
443
|
| Reference | Default base |
|
|
276
444
|
| --- | --- |
|
|
277
|
-
| Project `viewerUrl`, hotspot assets, `source.baseUrl` | Project directory |
|
|
445
|
+
| Project `viewerUrl`, hotspot assets, floor-plan image and spot icons, `source.baseUrl` | Project directory |
|
|
278
446
|
| Tiles and preview in external `viewer.json` | That JSON directory |
|
|
279
447
|
| Tiles and preview in inline config | Project directory |
|
|
280
448
|
| Panorama with `source.baseUrl` | Specified directory |
|
|
281
449
|
|
|
282
450
|
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.
|
|
283
451
|
|
|
284
|
-
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:
|
|
452
|
+
If uploads rename files or scatter directories, provide synchronous `resolveAssetUrl(path, context)`. Context has `defaultUrl`, `kind` (`tile`, `preview`, `hotspot`, `map`), `sceneId` and optional `hotspotId`. Return a final absolute URL or site-root path; it is not joined again. `uploadedUrls` below is application-owned:
|
|
285
453
|
|
|
286
454
|
```ts
|
|
287
455
|
const project = await loadPanoProject("/tour/project.json", {
|
|
@@ -293,13 +461,13 @@ const project = await loadPanoProject("/tour/project.json", {
|
|
|
293
461
|
});
|
|
294
462
|
```
|
|
295
463
|
|
|
296
|
-
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.
|
|
464
|
+
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. Floor-plan images and spot icons use the same callback with `kind: "map"`. The floor image has an empty `sceneId` and no `hotspotId`. A spot icon sets both `sceneId` and `hotspotId` to the spot id. Include `kind` in an upload key when one path is reused as more than one kind. Acquire signed URLs ahead of time in your application and return them synchronously.
|
|
297
465
|
|
|
298
466
|
`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`.
|
|
299
467
|
|
|
300
468
|
`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.
|
|
301
469
|
|
|
302
|
-
See the `/project` playground route and `public/fixtures/projects/tour/project.json` for a complete static example.
|
|
470
|
+
See the `/project` playground route and `public/fixtures/projects/tour/project.json` for a complete static example. `/map` shows the floor-plan overlay on that tour.
|
|
303
471
|
|
|
304
472
|
## krpano-compatible tile output
|
|
305
473
|
|
|
@@ -377,7 +545,8 @@ Import the shipped stylesheet once in the host app. The WebGL panorama
|
|
|
377
545
|
**HTML** overlays depend on its prefixed `pano-*` classes for layout.
|
|
378
546
|
|
|
379
547
|
Those overlays include video controls, captions, the default context menu,
|
|
380
|
-
hotspot tooltips, controls for assistive technology,
|
|
548
|
+
hotspot tooltips, controls for assistive technology, WebVR session UI, and the
|
|
549
|
+
floor-plan map.
|
|
381
550
|
|
|
382
551
|
```ts
|
|
383
552
|
import "@ericchen1990/pano-view/styles.css";
|
|
@@ -397,8 +566,9 @@ the stylesheet, the overlays still mount, but they render unstyled.
|
|
|
397
566
|
|
|
398
567
|
Give PanoViewer an explicit size and render one panorama source inside it.
|
|
399
568
|
Import the stylesheet once when you use built-in HTML overlays such as video
|
|
400
|
-
controls, captions, tooltips, the context menu,
|
|
401
|
-
below shows a basic setup; the linked section has the full
|
|
569
|
+
controls, captions, tooltips, the context menu, WebVR UI, or the floor-plan
|
|
570
|
+
map. Each example below shows a basic setup; the linked section has the full
|
|
571
|
+
props.
|
|
402
572
|
|
|
403
573
|
### Equirectangular image
|
|
404
574
|
|
|
@@ -513,9 +683,16 @@ Presets and hotspot rendering are in [Scenes](#scenes).
|
|
|
513
683
|
### Events and chrome
|
|
514
684
|
|
|
515
685
|
- [`PanoEvents`](#panoevents) — viewer-level lifecycle and interaction callbacks
|
|
686
|
+
- [`PanoOverlay`](#dom-overlays-and-dialogs) — screen-plane DOM wrapper for fullscreen-safe UI
|
|
516
687
|
- [`PanoContextMenu`](#panocontextmenu) — right-click reset / fullscreen menu
|
|
688
|
+
- [`PanoSceneMenu`](#panoscenemenu) — scrollable thumbnail scene picker for `PanoOverlay`
|
|
517
689
|
- [`PanoVideoControls`](#panovideo) — playback chrome for `PanoVideo` (mounted by default)
|
|
518
690
|
|
|
691
|
+
### Floor plan
|
|
692
|
+
|
|
693
|
+
- [`PanoMap`](#floor-plan-maps) — floor image, scene spots, and the yaw-linked sector
|
|
694
|
+
- [`PanoMapOverlay`](#floor-plan-maps) — docked panel or dialog launcher for `PanoOverlay`
|
|
695
|
+
|
|
519
696
|
### Hotspots
|
|
520
697
|
|
|
521
698
|
- [`ImageHotspot`](#imagehotspot) — image texture at a spherical position
|
|
@@ -539,9 +716,25 @@ Presets and hotspot rendering are in [Scenes](#scenes).
|
|
|
539
716
|
|
|
540
717
|
## DOM overlays and dialogs
|
|
541
718
|
|
|
542
|
-
|
|
719
|
+
Prefer `PanoOverlay` for ordinary DOM UI outside the Three.js tree. It mounts on the viewer plane inside the fullscreen root, so chrome stays visible when the viewer goes fullscreen. Place it as a direct child of `PanoViewer` (or inside a Fragment under it) beside scene content. `renderOverlay` remains supported for callback-style mounts.
|
|
543
720
|
|
|
544
|
-
|
|
721
|
+
```tsx
|
|
722
|
+
<PanoViewer style={{ height: 560 }}>
|
|
723
|
+
<Sphere src="/pano.jpg" />
|
|
724
|
+
<PanoOverlay>
|
|
725
|
+
<PanoSceneMenu items={items} activeId={id} onSelect={setId} />
|
|
726
|
+
<button
|
|
727
|
+
type="button"
|
|
728
|
+
style={{ position: "absolute", right: 16, bottom: 16, pointerEvents: "auto" }}
|
|
729
|
+
onClick={() => void viewer.toggleFullscreen()}
|
|
730
|
+
>
|
|
731
|
+
Fullscreen
|
|
732
|
+
</button>
|
|
733
|
+
</PanoOverlay>
|
|
734
|
+
</PanoViewer>
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
The overlay layer is `position: absolute; inset: 0; pointer-events: none`. Position children yourself and set `pointerEvents: "auto"` on interactive nodes. When both are used, `renderOverlay` renders first and each `PanoOverlay` follows in document order. Call `usePanoOverlay()` inside `PanoOverlay` (or `renderOverlay`) to get `portalContainer`, `size` (viewer width/height), `visibleRect`, and `isFullscreen`. `usePanoModal(open)` holds that viewer's modal lock while `open === true` (pauses input and auto-rotate, marks the backdrop inert, closes the context menu); it does not trap focus. `PanoOverlayPortalProvider` takes a required `container` and `children` and only remaps `portalContainer` for descendants; `size` / `visibleRect` still describe the whole viewer. Field meanings, reference counting, and third-party Dialog wiring live in the separate **[Dialog and styling guide](./docs/dialogs.md)**. The optional `/ui` entry provides `PanoDialog` and composable parts. The guide ships in the npm package; run `pnpm dev` and open `/overlays` for local examples.
|
|
545
738
|
|
|
546
739
|
### Default dialog
|
|
547
740
|
|
|
@@ -628,12 +821,27 @@ which use their spherical center. Hotspot ids must be unique within a viewer.
|
|
|
628
821
|
|
|
629
822
|
**Scenes** (requires `<Scenes />`) — `setScene(id)`, `nextScene`, `previousScene`, `getActiveSceneId`, `getSceneIds`, `isSceneTransitioning`. `Scenes` accepts `defaultActiveSceneId` (uncontrolled) or `activeSceneId` (controlled). In controlled mode imperative calls invoke `onActiveSceneIdChange`.
|
|
630
823
|
|
|
631
|
-
**WebVR** (requires `<WebVR />`) — `enterVR`, `exitVR`, `toggleVR`, `isVRAvailable`, `isVREnabled`, `getVRMode`, `requestVRPermission`.
|
|
824
|
+
**WebVR** (requires `<WebVR />`) — `enterVR`, `exitVR`, `toggleVR`, `isVRAvailable`, `isVREnabled`, `getVRMode`, `getVRState`, `subscribeVR`, `requestVRPermission`. Use `subscribeVR` with `useSyncExternalStore` for reactive Enter / Exit buttons; the built-in chrome no longer renders an Enter VR control.
|
|
632
825
|
|
|
633
826
|
**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`.
|
|
634
827
|
|
|
635
828
|
**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).
|
|
636
829
|
|
|
830
|
+
### Props
|
|
831
|
+
|
|
832
|
+
- `initialView` — when omitted, yaw `0`, pitch `0`, fov `75`. Partial objects fill the rest from those defaults.
|
|
833
|
+
- `minFov` / `maxFov` — default `30` / `100`. Used as FOV bounds when `viewLimits.fov` is not set.
|
|
834
|
+
- `viewLimits` — camera-center yaw / pitch / fov ranges; scene-level limits override matching axes. See [Camera-center limits](#camera-center-limits).
|
|
835
|
+
- `dpr` — canvas pixel ratio, default `[1, 2]` (clamped device pixel ratio). A single number is also accepted.
|
|
836
|
+
- `controls` — `false` disables all user input. An object configures shared options and `mouse` / `touch` / `keyboard` channels; see [Mouse, touch, and keyboard controls](#mouse-touch-and-keyboard-controls). Shared defaults: `enabled` `true`, `inertia` `true`, `invert` `false`, `bouncingLimits` `false`, `frictionStop` `0.01`, `rotateSpeed` `0.35`, `zoomSpeed` `0.08`, `rotateDamping` / `zoomDamping` `14` / `16`. Omit `fovSpeed` for no extra cap: mouse wheel clamps each step `deltaY * zoomSpeed` to `±fovSpeed`; keyboard zoom compares degrees-per-second × frame time against `fovSpeed`.
|
|
837
|
+
- `cursors` — `true` / omit uses grab / grabbing / pointer / move; `false` leaves the canvas cursor unchanged; an object overrides those four states.
|
|
838
|
+
- `contextMenu` — `true` / omit mounts Reset view + Fullscreen; `false` skips the default menu; an object can change appearance or `items` / `prepend` / `append`. See [PanoContextMenu](#panocontextmenu).
|
|
839
|
+
- `mediaActivation` — enabled by default when a mounted child intends to play; `false` disables automatic activation.
|
|
840
|
+
- `renderOverlay` — optional callback-style DOM UI outside the Three.js tree and inside fullscreen; the callback receives `portalContainer`, `size`, `visibleRect`, and `isFullscreen`. Prefer `<PanoOverlay>` as a child.
|
|
841
|
+
- `onViewChange` — `{ yaw, pitch, fov }` in degrees.
|
|
842
|
+
- `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` — panorama pointer events with `position` (yaw / pitch), `nativeEvent`, `button`, `buttons`, `clientX`, `clientY`, and optional `pointerId`.
|
|
843
|
+
- Other standard `div` props (`className`, `style`, `id`, …) are accepted; conflicting `onChange` and `contextMenu` are omitted from the type.
|
|
844
|
+
|
|
637
845
|
### Mobile media activation
|
|
638
846
|
|
|
639
847
|
By default, when a child has an initial playback intent (`PanoVideo autoPlay`, an active `BackgroundAudio`, `VideoHotspot`, or `AudioHotspot`), the viewer silently unlocks audible playback on the first user gesture when the browser requires one, or immediately when autoplay policy already allows it. Muted video can still preview inline. The same gesture resumes Web Audio and gives the host the final choice of which media becomes audible, so video, music, and positional sound never all start by assumption. Pass `mediaActivation={false}` to retain the 2.x no-automatic-activation behavior.
|
|
@@ -728,25 +936,29 @@ function SceneExample() {
|
|
|
728
936
|
### WebVR
|
|
729
937
|
|
|
730
938
|
`enterVR` and `requestVRPermission` must be called from a user gesture.
|
|
939
|
+
Drive entry from host UI with `getVRState` / `subscribeVR`; session chrome only
|
|
940
|
+
shows Exit VR and MobileVR Setup (which fade after a few idle seconds).
|
|
731
941
|
|
|
732
942
|
```tsx
|
|
943
|
+
import { useSyncExternalStore } from "react";
|
|
733
944
|
import { PanoViewer, Sphere, WebVR, usePanoViewer } from "@ericchen1990/pano-view";
|
|
734
945
|
|
|
735
946
|
function VRExample() {
|
|
736
947
|
const viewer = usePanoViewer();
|
|
948
|
+
const vr = useSyncExternalStore(
|
|
949
|
+
viewer.subscribeVR,
|
|
950
|
+
viewer.getVRState,
|
|
951
|
+
viewer.getVRState,
|
|
952
|
+
);
|
|
737
953
|
|
|
738
954
|
return (
|
|
739
955
|
<>
|
|
740
956
|
<button
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
await viewer.enterVR();
|
|
744
|
-
}}
|
|
957
|
+
disabled={!vr.available}
|
|
958
|
+
onClick={() => void (vr.enabled ? viewer.exitVR() : viewer.enterVR())}
|
|
745
959
|
>
|
|
746
|
-
Enter VR
|
|
960
|
+
{vr.enabled ? "Exit VR" : "Enter VR"}
|
|
747
961
|
</button>
|
|
748
|
-
<button onClick={() => void viewer.exitVR()}>Exit VR</button>
|
|
749
|
-
<button onClick={() => console.log(viewer.getVRMode())}>Mode?</button>
|
|
750
962
|
<PanoViewer ref={viewer.ref} style={{ height: 560 }}>
|
|
751
963
|
<WebVR />
|
|
752
964
|
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
@@ -924,7 +1136,7 @@ equirectangular tiles, use [`SphereTile`](#spherical-multiresolution-tiles).
|
|
|
924
1136
|
Provide a small preview to reduce download, decoding, and texture memory costs
|
|
925
1137
|
for the initial display.
|
|
926
1138
|
Relative paths resolve against the directory of `src`; root-absolute and
|
|
927
|
-
`http(s)` URLs are used as-is. For krpano sphere scenes, copy
|
|
1139
|
+
`http(s)`, `blob:`, and `data:` URLs are used as-is. For krpano sphere scenes, copy
|
|
928
1140
|
[`<preview url>`](https://krpano.com/docu/xml/#preview) into `previewUrl`. That
|
|
929
1141
|
preview must be equirectangular, not a cube strip.
|
|
930
1142
|
|
|
@@ -945,6 +1157,17 @@ export function SphereExample() {
|
|
|
945
1157
|
}
|
|
946
1158
|
```
|
|
947
1159
|
|
|
1160
|
+
### Props
|
|
1161
|
+
|
|
1162
|
+
- `src` — required full 2:1 equirectangular image.
|
|
1163
|
+
- `previewUrl` — required low-resolution 2:1 preview while `src` loads.
|
|
1164
|
+
- `yawOffset` — horizontal rotation in degrees, default `0`. Set when the source forward direction differs from viewer yaw `0`.
|
|
1165
|
+
- `visible` — default `true`. `false` keeps the source mounted for preload without drawing it.
|
|
1166
|
+
- `onReady` — once when the first paintable texture (preview or `src`) is ready.
|
|
1167
|
+
- `onLoad` — when the `src` texture finishes loading; argument is a Three.js `Texture`.
|
|
1168
|
+
- `onError` — when `src` fails to load.
|
|
1169
|
+
- `onPreviewError` — when the preview fails; `src` still continues loading.
|
|
1170
|
+
|
|
948
1171
|
## PanoVideo
|
|
949
1172
|
|
|
950
1173
|
`PanoVideo` maps a 2:1 equirectangular video onto the same inward sphere as
|
|
@@ -1025,6 +1248,30 @@ rejected. Remote videos and VTT files that are not same-origin should set
|
|
|
1025
1248
|
`crossOrigin` (usually `"anonymous"`). For mobile delivery, provide an H.264 +
|
|
1026
1249
|
AAC MP4 fallback, poster, CORS, and Range support.
|
|
1027
1250
|
|
|
1251
|
+
### Props
|
|
1252
|
+
|
|
1253
|
+
- `src` — single-file shortcut; mutually exclusive with `variants`.
|
|
1254
|
+
- `poster` — cover image. Variants may also set `variants[].poster`.
|
|
1255
|
+
- `variants` / `defaultVariantId` — multi-quality list; missing `defaultVariantId` falls back to the first item. Switching keeps current time and play/pause.
|
|
1256
|
+
- `tracks` — WebVTT tracks. Each may include `src`, `srcLang`, `label`, `default`, `id` (defaults to `srcLang`), and `kind` (`"subtitles"` or `"captions"`, default `"subtitles"`).
|
|
1257
|
+
- `captions` — `true` / omit shows the default overlay; `false` hides the overlay and language menu; an object merges with defaults. Defaults: `color` `#ffffff`, `background` `rgba(0, 0, 0, 0.55)`, `fontSize` `16`, `fontFamily` Inter / system-ui, `fontWeight` `500`, `textShadow` `0 1px 2px rgba(0, 0, 0, 0.8)`, `padding` `"6px 10px"`, `borderRadius` `6`, `maxWidth` `"72%"`, `bottom` `72`, `lineHeight` `1.35`.
|
|
1258
|
+
- `controls` — `true` / omit mounts the default bar; `false` hides it; an object restyles the default instance. Appearance keys: `background` (default `rgba(16, 16, 16, 0.88)`), `color` (`#f4f4f4`), `accent` (`#75cbd3`), `borderRadius` (`8`), `fontSize` (`12`), `padding` (`"8px 10px"`). Render `PanoVideoControls` as a `PanoViewer` child to replace the bar (same `appearance` prop).
|
|
1259
|
+
- `yawOffset` — horizontal offset in degrees, default `0`.
|
|
1260
|
+
- `loop` — default `false`.
|
|
1261
|
+
- `muted` — default `true`.
|
|
1262
|
+
- `volume` — default `1`, range 0–1.
|
|
1263
|
+
- `autoPlay` — default `false`. When `true`, counts as media-activation intent.
|
|
1264
|
+
- `playsInline` — default `true`.
|
|
1265
|
+
- `preload` — `"none"` | `"metadata"` | `"auto"`, default `"metadata"`.
|
|
1266
|
+
- `crossOrigin` — `""` | `"anonymous"` | `"use-credentials"`; omit to leave unset.
|
|
1267
|
+
- `playbackRates` — speed menu list, default `[0.5, 1, 1.25, 1.5, 2]`.
|
|
1268
|
+
- `visible` — default `true`; `false` preloads without drawing.
|
|
1269
|
+
- `onLoad` — when the video becomes playable.
|
|
1270
|
+
- `onError` — `{ source: "poster" | "video" | "track", error }`.
|
|
1271
|
+
- `onPlaybackStateChange` — `"playing" | "paused" | "ended" | "blocked"`.
|
|
1272
|
+
- `onEnded` — when playback reaches the end without looping.
|
|
1273
|
+
- `onVariantChange` / `onTrackChange` — current variant id, or current track id (`null` when none).
|
|
1274
|
+
|
|
1028
1275
|
## Tile
|
|
1029
1276
|
|
|
1030
1277
|
`Tile` renders six inward-facing cube faces and loads only tiles around the current view. When `urlTemplate` is omitted, paths relative to `baseUrl` use this package default:
|
|
@@ -1136,6 +1383,24 @@ horizontal index and `%00v` produces a three-digit vertical index. The stereo
|
|
|
1136
1383
|
`%t` and frame `%f` placeholders are not applicable to `Tile`, which represents
|
|
1137
1384
|
a single non-stereo cube panorama.
|
|
1138
1385
|
|
|
1386
|
+
### Props
|
|
1387
|
+
|
|
1388
|
+
- `baseUrl` / `multires` / `previewUrl` — required. `multires` may be a string or `{ tileSize, levels }`.
|
|
1389
|
+
- `previewFaceOrder` — default `["l", "f", "r", "b", "u", "d"]`; must list exactly six faces.
|
|
1390
|
+
- `urlTemplate` — package default when omitted; `resolveTileUrl` wins over the template.
|
|
1391
|
+
- `maxTextureMemoryMb` — standalone `Tile` default `128`.
|
|
1392
|
+
- `maxConcurrentLoads` — standalone `Tile` default `8` (do not confuse with `SphereTile` / `Scenes` concurrency of `4`).
|
|
1393
|
+
- `retryCount` — retries after the first failure, default `1`.
|
|
1394
|
+
- `loadMode` — `"full"` (default), `"preview"`, or `"base"`.
|
|
1395
|
+
- `visible` — default `true`; `false` preloads only.
|
|
1396
|
+
- `onReady` — when the preview is paintable (tiles may still report ready if the preview failed).
|
|
1397
|
+
- `onPreviewError` — preview load failure.
|
|
1398
|
+
- `onLoadProgress` — `{ requested, loaded, failed, active, queued }`.
|
|
1399
|
+
- `onTileError` — `{ address, url, error }`.
|
|
1400
|
+
- `onLevelChange` — current cube level, **1-based** (`SphereTile` logical levels are 0-based).
|
|
1401
|
+
|
|
1402
|
+
Inside `Scenes` with `type: "tile"`, the shared texture cache overrides per-scene `maxTextureMemoryMb`, `maxConcurrentLoads`, and `retryCount` (the shared provider fixes retry at `1`). Visibility, `loadMode`, and the callbacks above are owned by `Scenes` and should not be set on the scene object.
|
|
1403
|
+
|
|
1139
1404
|
## Scenes
|
|
1140
1405
|
|
|
1141
1406
|
`Scenes` switches between single-image equirectangular, cube-tile, and
|
|
@@ -1211,6 +1476,233 @@ While a detail wait or transition runs, panorama drag/zoom input is locked and
|
|
|
1211
1476
|
is still being prepared. `maxTextureMemoryMb` and `maxConcurrentTileLoads`
|
|
1212
1477
|
apply to the whole `Scenes` viewer rather than to each tile scene.
|
|
1213
1478
|
|
|
1479
|
+
### Props
|
|
1480
|
+
|
|
1481
|
+
- `scenes` — required. Supports `type: "sphere" | "tile" | "sphereTile"`.
|
|
1482
|
+
- `activeSceneId` — controlled current scene.
|
|
1483
|
+
- `defaultActiveSceneId` — uncontrolled initial scene. When both are omitted, uses `scenes[0].id`.
|
|
1484
|
+
- `onActiveSceneIdChange` — fired by imperative switches in controlled mode.
|
|
1485
|
+
- `transition` — defaults to `"crossfade"` for `1` second. Also accepts `{ preset, duration }`. Preset default seconds: `none` 0; `crossfade` / `whiteFlash` / most slides and opens / `pixelate` / `dissolve` / `clockWipe` / `zoomBlur` 1; `verticalOpen` 0.7; `glitch` 0.8; `hexDissolve` 1.1; the six `gridWipe*` / `shatter` / `swirl` / `ripple` 1.2; `filmBurn` 1.3; `particles` 1.4; `zoom` / `blackout` 2.
|
|
1486
|
+
- `transitionWaitForDetailMs` — default `0`.
|
|
1487
|
+
- `renderHotspots` — rendered only after a transition finishes; hidden while blending.
|
|
1488
|
+
- `onSceneReady` — first paintable scene, or incoming scene after snapshot takeover; not full detail.
|
|
1489
|
+
- `onTransitionEnd` — `{ previousSceneId, sceneId, preset }`.
|
|
1490
|
+
- `onTransitionError` — `{ sceneId, error }`.
|
|
1491
|
+
- `maxTextureMemoryMb` — shared budget, default `128`.
|
|
1492
|
+
- `maxConcurrentTileLoads` — shared concurrency, default `4`.
|
|
1493
|
+
- `snapshotMaxPixels` — max physical-pixel area of the outgoing GPU snapshot, default `3686400` (~2560×1440).
|
|
1494
|
+
|
|
1495
|
+
Scene `viewLimits` are covered in the FAQ; `yawOffset` matches the source component. Per-scene cube cache fields are ignored under the shared cache — see [Tile](#tile).
|
|
1496
|
+
|
|
1497
|
+
## PanoSceneMenu
|
|
1498
|
+
|
|
1499
|
+
`PanoSceneMenu` is a scrollable thumbnail strip for switching scenes. Mount it
|
|
1500
|
+
inside `PanoOverlay` so it stays inside the viewer (including fullscreen).
|
|
1501
|
+
Import `@ericchen1990/pano-view/styles.css` for the default layout.
|
|
1502
|
+
|
|
1503
|
+
The menu is host-driven: you pass `items`, `activeId`, and `onSelect`. It does
|
|
1504
|
+
not read `Scenes` state or scene `previewUrl`. Cube `previewUrl` values are
|
|
1505
|
+
often 1×6 preview strips and are not suitable thumbnails — use a dedicated
|
|
1506
|
+
thumbnail URL (for converter output, prefer something like `previews/200.webp`).
|
|
1507
|
+
|
|
1508
|
+
### Basic wiring
|
|
1509
|
+
|
|
1510
|
+
Keep one controlled scene id and share it with `Scenes`:
|
|
1511
|
+
|
|
1512
|
+
```tsx
|
|
1513
|
+
import { useState } from "react";
|
|
1514
|
+
import {
|
|
1515
|
+
PanoOverlay,
|
|
1516
|
+
PanoSceneMenu,
|
|
1517
|
+
PanoViewer,
|
|
1518
|
+
Scenes,
|
|
1519
|
+
type PanoSceneMenuItem,
|
|
1520
|
+
} from "@ericchen1990/pano-view";
|
|
1521
|
+
import "@ericchen1990/pano-view/styles.css";
|
|
1522
|
+
|
|
1523
|
+
const menuItems: PanoSceneMenuItem[] = [
|
|
1524
|
+
{
|
|
1525
|
+
id: "lobby",
|
|
1526
|
+
title: "Lobby",
|
|
1527
|
+
thumbnailUrl: "/panoramas/lobby/thumb.webp",
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
id: "terrace",
|
|
1531
|
+
title: "Terrace",
|
|
1532
|
+
thumbnailUrl: "/panoramas/terrace/previews/200.webp",
|
|
1533
|
+
},
|
|
1534
|
+
];
|
|
1535
|
+
|
|
1536
|
+
export function Tour() {
|
|
1537
|
+
const [activeSceneId, setActiveSceneId] = useState("lobby");
|
|
1538
|
+
|
|
1539
|
+
return (
|
|
1540
|
+
<PanoViewer style={{ height: 560 }}>
|
|
1541
|
+
<Scenes
|
|
1542
|
+
scenes={scenes}
|
|
1543
|
+
activeSceneId={activeSceneId}
|
|
1544
|
+
onActiveSceneIdChange={setActiveSceneId}
|
|
1545
|
+
/>
|
|
1546
|
+
<PanoOverlay>
|
|
1547
|
+
<PanoSceneMenu
|
|
1548
|
+
activeId={activeSceneId}
|
|
1549
|
+
items={menuItems}
|
|
1550
|
+
onSelect={setActiveSceneId}
|
|
1551
|
+
/>
|
|
1552
|
+
</PanoOverlay>
|
|
1553
|
+
</PanoViewer>
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
Each item needs `id` and `thumbnailUrl`. Optional `title` is shown on the
|
|
1559
|
+
thumbnail label and as the control tooltip; when omitted, `id` is used. The
|
|
1560
|
+
active item gets a selection ring above the thumbnail and `data-active="true"`.
|
|
1561
|
+
Scene names sit on a dark translucent bar at the bottom of each thumbnail;
|
|
1562
|
+
use `appearance.labelAlign` for `"center"` or `"end"` alignment.
|
|
1563
|
+
|
|
1564
|
+
### Placement, inset, and size
|
|
1565
|
+
|
|
1566
|
+
- `placement` — `"top"` | `"bottom"` | `"left"` | `"right"` (default `"bottom"`).
|
|
1567
|
+
Top/bottom render a horizontal strip; left/right render a vertical strip.
|
|
1568
|
+
- `align` — `"start"` | `"center"` | `"end"` (default `"center"`). When the menu
|
|
1569
|
+
is smaller than the available dock space, aligns it along that edge. Short
|
|
1570
|
+
lists are also aligned inside the strip the same way.
|
|
1571
|
+
- `inset` — gap from the viewer edges (default `12`). A number is pixels; a
|
|
1572
|
+
string keeps units such as `"1rem"`; an object sets `top` / `right` /
|
|
1573
|
+
`bottom` / `left` independently.
|
|
1574
|
+
- `width` / `height` — menu size. A number is pixels; a string is used as CSS as-is,
|
|
1575
|
+
including `%`, `rem`, `calc()`, `min()`, `max()`, and `clamp()`.
|
|
1576
|
+
- `dragMotion` — scroll inertia and edge bounce. Omit for both on (custom pointer
|
|
1577
|
+
scrolling, including desktop drag). Set `false` for native overflow scroll.
|
|
1578
|
+
Pass `{ inertia?: boolean; bounce?: boolean }` to toggle either flag. A short
|
|
1579
|
+
press still selects; a drag past a few pixels suppresses the click.
|
|
1580
|
+
|
|
1581
|
+
```tsx
|
|
1582
|
+
<PanoSceneMenu
|
|
1583
|
+
placement="bottom"
|
|
1584
|
+
align="center"
|
|
1585
|
+
inset={16}
|
|
1586
|
+
width="min(720px, 100%)"
|
|
1587
|
+
items={menuItems}
|
|
1588
|
+
activeId={activeSceneId}
|
|
1589
|
+
onSelect={setActiveSceneId}
|
|
1590
|
+
/>
|
|
1591
|
+
|
|
1592
|
+
<PanoSceneMenu
|
|
1593
|
+
placement="right"
|
|
1594
|
+
align="start"
|
|
1595
|
+
inset={{ top: 24, right: 12, bottom: 24, left: 12 }}
|
|
1596
|
+
height="70%"
|
|
1597
|
+
width={160}
|
|
1598
|
+
items={menuItems}
|
|
1599
|
+
activeId={activeSceneId}
|
|
1600
|
+
onSelect={setActiveSceneId}
|
|
1601
|
+
/>
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
If you omit `width` on a top/bottom menu, the strip stretches between the left
|
|
1605
|
+
and right insets. If you omit `height` on a left/right menu, it stretches between
|
|
1606
|
+
the top and bottom insets. The menu also applies a max size that subtracts
|
|
1607
|
+
`inset` and safe-area insets, so `100%` still stays inside the viewer.
|
|
1608
|
+
|
|
1609
|
+
Use `style` when you need one-off positioning overrides on top of these props.
|
|
1610
|
+
|
|
1611
|
+
### Collapsible menu and arrows
|
|
1612
|
+
|
|
1613
|
+
By default the menu stays open. Set `collapsible` to show a toggle centered on
|
|
1614
|
+
the inner edge of the strip (above a bottom menu, below a top menu, toward the
|
|
1615
|
+
viewer for left/right). Collapsing slides the list off-screen along the dock
|
|
1616
|
+
edge and leaves only the expand button on the safe-area edge:
|
|
1617
|
+
|
|
1618
|
+
```tsx
|
|
1619
|
+
<PanoSceneMenu
|
|
1620
|
+
collapsible
|
|
1621
|
+
defaultExpanded
|
|
1622
|
+
items={menuItems}
|
|
1623
|
+
activeId={activeSceneId}
|
|
1624
|
+
onSelect={setActiveSceneId}
|
|
1625
|
+
/>
|
|
1626
|
+
|
|
1627
|
+
// Controlled
|
|
1628
|
+
<PanoSceneMenu
|
|
1629
|
+
collapsible
|
|
1630
|
+
expanded={menuOpen}
|
|
1631
|
+
onExpandedChange={setMenuOpen}
|
|
1632
|
+
items={menuItems}
|
|
1633
|
+
activeId={activeSceneId}
|
|
1634
|
+
onSelect={setActiveSceneId}
|
|
1635
|
+
/>
|
|
1636
|
+
```
|
|
1637
|
+
|
|
1638
|
+
Set `showArrows` to add previous/next buttons at both ends of the list. By
|
|
1639
|
+
default the buttons stop at the first and last items. Set `loop` so arrows and
|
|
1640
|
+
arrow keys wrap from the last item to the first (and vice versa). Arrows stay
|
|
1641
|
+
disabled when there are fewer than two scenes. Customize their accessible names
|
|
1642
|
+
with `previousLabel` / `nextLabel`, and the toggle with `expandLabel` /
|
|
1643
|
+
`collapseLabel`. Defaults are `"Previous scene"`, `"Next scene"`,
|
|
1644
|
+
`"Show scenes"`, and `"Hide scenes"`. The radiogroup label defaults to `"Scenes"`
|
|
1645
|
+
via `label`. When collapsible, `defaultExpanded` defaults to `true`.
|
|
1646
|
+
|
|
1647
|
+
```tsx
|
|
1648
|
+
<PanoSceneMenu
|
|
1649
|
+
showArrows
|
|
1650
|
+
loop
|
|
1651
|
+
collapsible
|
|
1652
|
+
placement="bottom"
|
|
1653
|
+
width="min(720px, 100%)"
|
|
1654
|
+
items={menuItems}
|
|
1655
|
+
activeId={activeSceneId}
|
|
1656
|
+
onSelect={setActiveSceneId}
|
|
1657
|
+
previousLabel="Previous room"
|
|
1658
|
+
nextLabel="Next room"
|
|
1659
|
+
/>
|
|
1660
|
+
```
|
|
1661
|
+
|
|
1662
|
+
### Appearance and CSS hooks
|
|
1663
|
+
|
|
1664
|
+
Pass `appearance` for common chrome tokens without writing CSS. The active-item
|
|
1665
|
+
ring sits above the thumbnail and is controlled by `selectionColor` /
|
|
1666
|
+
`selectionWidth` (`selectionWidth: 0` hides it). Scene titles default to start
|
|
1667
|
+
alignment; set `labelAlign: "center"` or `"end"` to change that.
|
|
1668
|
+
|
|
1669
|
+
```tsx
|
|
1670
|
+
<PanoSceneMenu
|
|
1671
|
+
appearance={{
|
|
1672
|
+
background: "rgba(8, 12, 18, 0.88)",
|
|
1673
|
+
itemWidth: 144,
|
|
1674
|
+
itemHeight: 88,
|
|
1675
|
+
gap: 10,
|
|
1676
|
+
borderRadius: 10,
|
|
1677
|
+
selectionColor: "#7dd3fc",
|
|
1678
|
+
selectionWidth: 2,
|
|
1679
|
+
labelBackground: "rgba(0, 0, 0, 0.6)",
|
|
1680
|
+
labelColor: "#ffffff",
|
|
1681
|
+
labelAlign: "center",
|
|
1682
|
+
fontSize: 12,
|
|
1683
|
+
controlColor: "#f8fafc",
|
|
1684
|
+
controlBackground: "rgba(255, 255, 255, 0.14)",
|
|
1685
|
+
}}
|
|
1686
|
+
items={menuItems}
|
|
1687
|
+
activeId={activeSceneId}
|
|
1688
|
+
onSelect={setActiveSceneId}
|
|
1689
|
+
/>
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
`DEFAULT_PANO_SCENE_MENU_APPEARANCE` exports the defaults (including
|
|
1693
|
+
`padding: "8px 10px"` and `labelAlign: "start"`). For deeper styling,
|
|
1694
|
+
use `className` / `style` against these stable classes:
|
|
1695
|
+
|
|
1696
|
+
- `pano-scene-menu` — root chrome
|
|
1697
|
+
- `pano-scene-menu-body` — arrows + list row/column (owns the strip background)
|
|
1698
|
+
- `pano-scene-menu-list` — scrollable item list
|
|
1699
|
+
- `pano-scene-menu-item` / `pano-scene-menu-thumb` / `pano-scene-menu-label`
|
|
1700
|
+
- `pano-scene-menu-control` / `pano-scene-menu-arrow` / `pano-scene-menu-toggle`
|
|
1701
|
+
|
|
1702
|
+
The root also exposes `data-placement`, `data-align`, `data-orientation`
|
|
1703
|
+
(`horizontal` | `vertical`), `data-expanded`, and `data-collapsible`. The active
|
|
1704
|
+
item has `data-active="true"`.
|
|
1705
|
+
|
|
1214
1706
|
## PanoFilter
|
|
1215
1707
|
|
|
1216
1708
|
Render `PanoFilter` inside `PanoViewer` to color-grade or stylize the panorama
|
|
@@ -1257,7 +1749,7 @@ panorama when a filter is active.
|
|
|
1257
1749
|
|
|
1258
1750
|
## AutoRotate
|
|
1259
1751
|
|
|
1260
|
-
Auto-rotation is off by default. Render `AutoRotate` inside `PanoViewer` to enable it and keep rotation configuration separate from user-input controls. `speed` is measured in degrees per second; use a negative value to rotate left. `acceleration` is measured in degrees per second squared and smoothly ramps from zero to `speed` (default: `18`, so the default speed takes one second to reach). Set it to `0` for an immediate fixed speed. `startDelay` is measured in milliseconds from when `enabled` becomes true. While the user is dragging, or while drag inertia is still settling, rotation pauses and resumes from zero speed automatically.
|
|
1752
|
+
Auto-rotation is off by default. Render `AutoRotate` inside `PanoViewer` to enable it and keep rotation configuration separate from user-input controls. `speed` is measured in degrees per second; use a negative value to rotate left. `acceleration` is measured in degrees per second squared and smoothly ramps from zero to `speed` (default: `18`, so the default speed takes one second to reach). Set it to `0` for an immediate fixed speed. `startDelay` is measured in milliseconds from when `enabled` becomes true (default `2`). While the user is dragging, or while drag inertia is still settling, rotation pauses and resumes from zero speed automatically. When the component is mounted and `enabled` is omitted, it is treated as `true`; omitting the component entirely means no auto-rotation.
|
|
1261
1753
|
|
|
1262
1754
|
```tsx
|
|
1263
1755
|
<PanoViewer style={{ height: 560 }}>
|
|
@@ -1332,6 +1824,22 @@ every other scene plays `default.mp3`.
|
|
|
1332
1824
|
For a sound attached to a point in the panorama that pans with the view, use
|
|
1333
1825
|
`AudioHotspot`, not `BackgroundAudio`.
|
|
1334
1826
|
|
|
1827
|
+
### Props
|
|
1828
|
+
|
|
1829
|
+
- `src` / `sources` — `src` is the shared track, or the fallback when a scene is missing from `sources`. Either value may be a string or a string array (Howler multi-format fallback). `sources` keys are `Scene.id` values.
|
|
1830
|
+
- `sceneId` — required when `sources` is set; omitting it throws.
|
|
1831
|
+
- `playing` / `defaultPlaying` — controlled playback, or uncontrolled initial value (default `false`).
|
|
1832
|
+
- `loop` — default `true`.
|
|
1833
|
+
- `muted` — default `false`.
|
|
1834
|
+
- `volume` — default `1`, range 0–1.
|
|
1835
|
+
- `pauseWhenHidden` — default `true`; pauses while the page is hidden.
|
|
1836
|
+
- `fadeMs` — crossfade milliseconds when the resolved track changes, default `400`; `0` is a hard cut.
|
|
1837
|
+
- `crossOrigin` — `""` | `"anonymous"` | `"use-credentials"`; omit to leave unset.
|
|
1838
|
+
- `onPlaybackStateChange` — `"playing" | "paused" | "ended" | "blocked"`.
|
|
1839
|
+
- `onPlaybackError` — `play()` and other playback failures.
|
|
1840
|
+
- `onEnded` — when the track finishes; uncontrolled mode also sets internal playing to `false`.
|
|
1841
|
+
- `onError` — resource load failure, `{ error }` (distinct from `onPlaybackError`).
|
|
1842
|
+
|
|
1335
1843
|
## Gyro
|
|
1336
1844
|
|
|
1337
1845
|
`Gyro` is an opt-in device-orientation control and is disabled by default.
|
|
@@ -1405,13 +1913,30 @@ require HTTPS. A cross-origin iframe must be permitted by the host page, for
|
|
|
1405
1913
|
example with `allow="gyroscope; accelerometer"` and a compatible
|
|
1406
1914
|
`Permissions-Policy` header.
|
|
1407
1915
|
|
|
1916
|
+
### Props
|
|
1917
|
+
|
|
1918
|
+
- `enabled` — default `false`. On iOS, call `requestPermission()` inside a user gesture before setting `true`.
|
|
1919
|
+
- `camroll` — default `true`.
|
|
1920
|
+
- `absolute` — default `false` (relative mode); `true` follows the compass.
|
|
1921
|
+
- `north` — panorama yaw that represents North, in degrees, default `0`.
|
|
1922
|
+
- `friction` — `0`–`0.99`, default `0`.
|
|
1923
|
+
- `touchMode` — default `"full"`; see values above.
|
|
1924
|
+
- `softstart` — blend seconds when enabling, default `0.5`.
|
|
1925
|
+
- `desktopSupport` — default `false`.
|
|
1926
|
+
- `onAvailable` / `onUnavailable` — when sensor capability detection changes.
|
|
1927
|
+
- `onEnable` / `onDisable` — when a session actually starts / stops.
|
|
1928
|
+
- `onDenied` — when permission is denied (examples often set `enabled` back to `false`).
|
|
1929
|
+
|
|
1930
|
+
These are props on `Gyro` itself. `PanoEvents` `onGyro*` callbacks subscribe to the same event bus for viewer-level listeners.
|
|
1931
|
+
|
|
1408
1932
|
## WebVR
|
|
1409
1933
|
|
|
1410
1934
|
`WebVR` adds immersive viewing to the nearest `PanoViewer`. It prefers an
|
|
1411
1935
|
`immersive-vr` WebXR session on supported headsets. On phones without WebXR it
|
|
1412
1936
|
uses a stereoscopic split-screen view with device orientation, and on desktop
|
|
1413
|
-
it can provide a mouse-driven simulated VR preview.
|
|
1414
|
-
|
|
1937
|
+
it can provide a mouse-driven simulated VR preview. Host apps provide Enter VR
|
|
1938
|
+
through the viewer handle; the built-in chrome only shows Exit VR and MobileVR
|
|
1939
|
+
setup controls while a session is active (they fade after idle interaction).
|
|
1415
1940
|
|
|
1416
1941
|
```tsx
|
|
1417
1942
|
import { useRef } from "react";
|
|
@@ -1441,19 +1966,28 @@ export function WebVRExample() {
|
|
|
1441
1966
|
```
|
|
1442
1967
|
|
|
1443
1968
|
The handle exposes `enterVR()`, `exitVR()`, `toggleVR()`, `isAvailable()`,
|
|
1444
|
-
`isEnabled()`, `getMode()`,
|
|
1445
|
-
|
|
1446
|
-
`"cardboard-
|
|
1447
|
-
`{ fov, ipdMm, k1, k2 }`
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
`
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1969
|
+
`isEnabled()`, `getMode()`, `getState()`, `subscribe()`, `requestPermission()`,
|
|
1970
|
+
and `resetSettings()`. Modes are `"webxr"`, `"mobilevr"`, and `"fake"`.
|
|
1971
|
+
`profile` accepts `"none"`, `"cardboard-v1"`, `"cardboard-v2"`, `"gear-vr"`,
|
|
1972
|
+
`"daydream"`, or a custom `{ fov, ipdMm, k1, k2, screenToLensMm?, trayDistanceMm? }`
|
|
1973
|
+
lens profile. When the physical screen size is known, MobileVR places each eye's
|
|
1974
|
+
optical axis with the inter-lens distance and optional tray-to-lens height
|
|
1975
|
+
(`trayDistanceMm`; Cardboard V1 uses `35`). `0` or omitted keeps the vertical
|
|
1976
|
+
center. When `screensize` is `"auto"`, MobileVR loads the Cardboard Device
|
|
1977
|
+
Parameter Database (`databaseUrl`, default Google's public DPDB) to estimate
|
|
1978
|
+
diagonal inches; pass `""` or `null` to skip the request. Unknown devices fire
|
|
1979
|
+
`onUnknownDevice` and keep the lens-profile FOV (no tray offset until a size is
|
|
1980
|
+
known). MobileVR setup values are versioned and stored in the current origin's
|
|
1981
|
+
`localStorage`; `resetSettings()` clears that storage and restores prop or
|
|
1982
|
+
built-in defaults (also available from the Setup panel Reset control). In VR, a
|
|
1983
|
+
center reticle shows a crosshair; looking at an interactive hotspot draws a
|
|
1984
|
+
clockwise ring that fills over `cursorDwellMs` (default 1500), then the hotspot
|
|
1985
|
+
click handler runs. Set `cursorDwellMs={0}` to keep the reticle without
|
|
1986
|
+
auto-click. Simulated desktop VR (`fake` mode) requests pointer lock by default
|
|
1987
|
+
(`mousePointerLock`) so the cursor hides and mouse movement looks around; click
|
|
1988
|
+
the view again if the browser releases the lock. Fallback sessions keep the
|
|
1989
|
+
screen awake (`wakelock`) with the Screen Wake Lock API, or a looping hidden
|
|
1990
|
+
video on browsers that lack it.
|
|
1457
1991
|
|
|
1458
1992
|
WebXR requires HTTPS and a user gesture. Mobile sensor access also requires
|
|
1459
1993
|
HTTPS and may require an explicit iOS permission prompt. Embedded viewers need
|
|
@@ -1462,6 +1996,30 @@ DOM overlays such as iframe hotspots, tooltips, and video controls are not
|
|
|
1462
1996
|
visible inside an immersive headset session; mesh-based hotspots remain
|
|
1463
1997
|
interactive through the default WebXR controller rays.
|
|
1464
1998
|
|
|
1999
|
+
### Props
|
|
2000
|
+
|
|
2001
|
+
- `chrome` — default `true` mounts session Exit / Setup UI (no Enter VR button). An appearance object may set `background` (default `rgba(16, 24, 32, 0.78)`), `color` (`#ffffff`), `accent` (`#7dd3fc`), `borderRadius` (`6`), `fontSize` (`14`), and optional `setupTitle`. Session buttons fade after ~3s idle and return on pointer / wheel / keyboard interaction with the viewer.
|
|
2002
|
+
- `mobileVr` — default `true`.
|
|
2003
|
+
- `desktopSupport` — default `false` (independent of the same-named `Gyro` prop).
|
|
2004
|
+
- `fakeSupport` — default `true` for mouse-driven desktop simulation.
|
|
2005
|
+
- `screensize` — `"auto"` (factory / omitted storage default) or inches; numbers are clamped to **3–20** with one decimal place.
|
|
2006
|
+
- `databaseUrl` — Cardboard DPDB URL for `"auto"` screensize. Default `https://storage.googleapis.com/cardboard-dpdb/dpdb.json`. Pass `""` or `null` to disable the online lookup.
|
|
2007
|
+
- `profile` — omitted values use stored settings; factory default is `"cardboard-v1"`. Also accepts `"none"`, `"cardboard-v2"`, `"gear-vr"`, `"daydream"`, or a custom `{ fov, ipdMm, k1, k2, screenToLensMm?, trayDistanceMm?, label?, id? }`. `trayDistanceMm` is millimetres from the headset tray bottom to the lens center; `0` / omitted keeps the vertical optical axis at screen center.
|
|
2008
|
+
- `absolute` — default `false`. Compass-oriented MobileVR heading (no effect in WebXR).
|
|
2009
|
+
- `north` — default `0`. Panorama yaw that represents North when `absolute` is enabled.
|
|
2010
|
+
- `friction` — default `0` (range `0–0.99`). MobileVR gyro smoothing; keep `0` for normal viewing.
|
|
2011
|
+
- `touchSupport` — default `false`. When `true`, allows horizontal touch dragging to correct MobileVR heading (`horizontaloffset`). Disabled by default because headset trays can trigger false touches.
|
|
2012
|
+
- `oversampling` — default `1`, clamped to **0.5–2**. Scales the MobileVR distortion render target only (viewports unchanged). For WebXR, sets the framebuffer scale factor before the session starts (cannot change mid-session).
|
|
2013
|
+
- `foveation` — optional WebXR fixed foveation in **0–1**. Omit to keep the device default; `0` is full resolution, `1` is maximum edge downsampling.
|
|
2014
|
+
- `frameRate` — optional WebXR target rate: `"low" | "mid" | "high"` or a numeric Hz (closest supported rate). Omit to keep the store default (`"high"`).
|
|
2015
|
+
- `cursor` — default `true`; `false` hides the center reticle.
|
|
2016
|
+
- `cursorDwellMs` — default `1500`; `0` disables auto-click.
|
|
2017
|
+
- `wakelock` / `mousePointerLock` — both default to `true`.
|
|
2018
|
+
- `onAvailable` / `onUnavailable` — VR capability changes.
|
|
2019
|
+
- `onEnterVR` / `onExitVR` — mode `"webxr" | "mobilevr" | "fake"`.
|
|
2020
|
+
- `onDenied` — permission or session denial, optional `error`.
|
|
2021
|
+
- `onUnknownDevice` — when `"auto"` screensize cannot be resolved from the DPDB (disabled URL, no match, or fetch failure).
|
|
2022
|
+
|
|
1465
2023
|
## Mouse, touch, and keyboard controls
|
|
1466
2024
|
|
|
1467
2025
|
`PanoViewer` enables `MouseControls`, `TouchControls`, and `KeyboardControls` by default. Configure shared options through `controls`; render a control component only to override that channel's properties. A child instance replaces the default — you do not need to set the channel to `false` first. Set `controls.mouse` / `touch` / `keyboard` to `false` to turn a channel off.
|
|
@@ -1485,15 +2043,36 @@ interactive through the default WebXR controller rays.
|
|
|
1485
2043
|
|
|
1486
2044
|
### MouseControls
|
|
1487
2045
|
|
|
1488
|
-
|
|
2046
|
+
Override the default channel through `controls.mouse` or by rendering `<MouseControls />` as a child. A child instance does **not** inherit `controls.fovSpeed`; pass `fovSpeed` on the component when you need the cap.
|
|
2047
|
+
|
|
2048
|
+
- `enabled` — default `true`.
|
|
2049
|
+
- `rotateSpeed` — default `0.35` (scales pointer displacement; `0` disables rotate).
|
|
2050
|
+
- `zoomSpeed` — default `0.08`; each wheel step is `deltaY * zoomSpeed`.
|
|
2051
|
+
- `wheel` — default `true`.
|
|
2052
|
+
- `invert` — default `false`; may override shared `controls.invert`.
|
|
2053
|
+
- `buttons` — default `["left"]`; values `"left" | "middle" | "right"`.
|
|
2054
|
+
- `fovSpeed` — omit for no cap; when set, clamps each wheel step to `±fovSpeed` (not a per-second rate).
|
|
1489
2055
|
|
|
1490
2056
|
### TouchControls
|
|
1491
2057
|
|
|
1492
|
-
One-finger drag and optional two-finger pinch zoom
|
|
2058
|
+
One-finger drag and optional two-finger pinch zoom. Override through `controls.touch` or by rendering `<TouchControls />`.
|
|
2059
|
+
|
|
2060
|
+
- `enabled` — default `true`.
|
|
2061
|
+
- `rotateSpeed` — default `0.35`, same meaning as mouse.
|
|
2062
|
+
- `invert` — default `false`.
|
|
2063
|
+
- `pinchZoom` — default `true`. Pinch changes FOV from the finger-distance ratio and is not limited by `fovSpeed`.
|
|
1493
2064
|
|
|
1494
2065
|
### KeyboardControls
|
|
1495
2066
|
|
|
1496
|
-
Hold arrows (or custom bindings) for continuous look / FOV
|
|
2067
|
+
Hold arrows (or custom bindings) for continuous look / FOV. Override through `controls.keyboard` or by rendering `<KeyboardControls />`. Child instances likewise do not inherit `controls.fovSpeed`.
|
|
2068
|
+
|
|
2069
|
+
- `enabled` — default `true`.
|
|
2070
|
+
- `rotateSpeed` / `zoomSpeed` — default `60` / `30` (degrees per second).
|
|
2071
|
+
- `shiftMultiplier` — default `3`.
|
|
2072
|
+
- `invert` — default `false`; flips up/down only.
|
|
2073
|
+
- `fovSpeed` — omit for no cap; when set, limits with `min(zoom, fovSpeed * dt)`.
|
|
2074
|
+
- `keys` — replaces an action wholesale when that key is present; omitted actions keep defaults; an empty array unbinds the action. Matching compares both `event.key` and `event.code` (lowercased).
|
|
2075
|
+
- `onPreviousScene` / `onNextScene` — keys still `preventDefault` when bound; without a callback the scene does not change.
|
|
1497
2076
|
|
|
1498
2077
|
Shared all-mode options on `controls`: `enabled`, `invert` (drag direction for mouse/touch), `bouncingLimits`, `fovSpeed`, `frictionStop` (default `0.01`), plus existing damping / inertia.
|
|
1499
2078
|
|
|
@@ -1546,7 +2125,7 @@ export function KeyboardExample({ scenes }: { scenes: Scene[] }) {
|
|
|
1546
2125
|
|
|
1547
2126
|
Hold movement and zoom keys for continuous motion in degrees per second (`rotateSpeed` / `zoomSpeed`). Hold Shift to multiply those rates (`shiftMultiplier`, default `3`). Scene and reset bindings fire once per press. The canvas must be focused to receive keys (click the viewer first).
|
|
1548
2127
|
|
|
1549
|
-
Default bindings: arrows and WASD for look
|
|
2128
|
+
Default bindings: arrows and WASD for look; zoom in with `+` and `=`, zoom out with `-` and `_`; `0` resets; `[` / `PageUp` and `]` / `PageDown` for previous/next scene (without callbacks the scene does not change, but the key may still be intercepted). The same child-override pattern works with `MouseControls` and `TouchControls`. Set `controls.mouse={false}` / `controls.touch={false}` / `controls.keyboard={false}` only when you want that channel off.
|
|
1550
2129
|
|
|
1551
2130
|
## Panorama coordinate events
|
|
1552
2131
|
|
|
@@ -1622,25 +2201,28 @@ import {
|
|
|
1622
2201
|
|
|
1623
2202
|
Supported callbacks (krpano names in parentheses where applicable):
|
|
1624
2203
|
|
|
1625
|
-
- View: `onViewChange` (`onviewchange`), `onViewSettled
|
|
1626
|
-
-
|
|
1627
|
-
- `
|
|
2204
|
+
- View: `onViewChange` (`onviewchange`), `onViewSettled` — both receive `{ yaw, pitch, fov }`. `viewchange` fires while the view moves; `viewsettled` fires when inertia/tween first stops or a commanded animation ends.
|
|
2205
|
+
- `onViewInteractionStart` / `onViewInteractionEnd` — `{ source }` with `source` `"mouse" | "touch" | "keyboard"`.
|
|
2206
|
+
- Pointer on the panorama shell (not hotspots): `onClick`, `onDoubleClick`, `onPointerDown`, `onPointerUp`, `onPointerMove`, `onContextMenu` — `PanoramaPointerEvent` with `position`, `nativeEvent`, `button`, `buttons`, `clientX`, `clientY`, and optional `pointerId`.
|
|
2207
|
+
- `onWheel` — mouse wheel only; touch pinch does not synthesize a wheel event. Payload `{ delta, nativeEvent }`; `delta` matches the sign of `WheelEvent.deltaY`.
|
|
1628
2208
|
- Idle: `onIdle` / `onIdleEnd` with per-instance `idleTime` (ms, default `2000`)
|
|
1629
|
-
- Fullscreen: `onEnterFullscreen` / `onExitFullscreen`
|
|
1630
|
-
- `onResize` —
|
|
1631
|
-
- Auto-rotate: `onAutoRotateStart` / `onAutoRotateStop` / `onAutoRotateOneRound`
|
|
1632
|
-
- Gyro: `onGyroAvailable` / `onGyroUnavailable` / `onGyroEnable` /
|
|
1633
|
-
|
|
2209
|
+
- Fullscreen: `onEnterFullscreen` / `onExitFullscreen` (no payload)
|
|
2210
|
+
- `onResize` — `{ width, height }` from a content-box `ResizeObserver`
|
|
2211
|
+
- Auto-rotate: `onAutoRotateStart` / `onAutoRotateStop` / `onAutoRotateOneRound` (no payload)
|
|
2212
|
+
- Gyro: `onGyroAvailable` / `onGyroUnavailable` / `onGyroEnable` / `onGyroDisable` / `onGyroDenied` (no payload)
|
|
2213
|
+
- VR: `onVRAvailable` / `onVRUnavailable` / `onVREnter` / `onVRExit` / `onVRDenied` / `onVRUnknownDevice`. Enter/exit carry mode `"webxr" | "mobilevr" | "fake"`; `onVRDenied` may include an optional `error`.
|
|
1634
2214
|
|
|
1635
2215
|
`PanoViewer`'s existing `onViewChange` / `onPanoramaClick` / `onPanoramaDoubleClick` /
|
|
1636
2216
|
`onPanoramaPointerMove` props remain supported and share the same event bus.
|
|
2217
|
+
`usePanoEvents` accepts the same props as `<PanoEvents>` and returns nothing.
|
|
1637
2218
|
|
|
1638
2219
|
Resource callbacks belong to each source: `Sphere` provides `onLoad`,
|
|
1639
2220
|
`onError`, and `onPreviewError`; `Tile` / `SphereTile` provide `onLoadProgress`,
|
|
1640
2221
|
`onTileError`, and `onPreviewError`. Sources report initial render readiness
|
|
1641
2222
|
through `onReady`. Use `Scenes` callbacks `onSceneReady`, `onTransitionEnd`,
|
|
1642
|
-
and `onTransitionError` for scene state. This package does not
|
|
1643
|
-
|
|
2223
|
+
and `onTransitionError` for scene state. This package does not mirror krpano
|
|
2224
|
+
XML-loading or frame-render events one-to-one; use the `onVR*` callbacks above
|
|
2225
|
+
for VR lifecycle.
|
|
1644
2226
|
|
|
1645
2227
|
## PanoContextMenu
|
|
1646
2228
|
|
|
@@ -1734,18 +2316,32 @@ reimplementing fullscreen enter/exit state:
|
|
|
1734
2316
|
</PanoViewer>
|
|
1735
2317
|
```
|
|
1736
2318
|
|
|
1737
|
-
Override presentation on a preset with `{ preset: "fullscreen", label: "…" }
|
|
2319
|
+
Override presentation on a preset with `{ preset: "fullscreen", label: "…" }`;
|
|
2320
|
+
presets may also override `id`, `className`, `style`, `disabled`, and `hidden`.
|
|
1738
2321
|
For fully manual assembly outside the prop (for example a custom
|
|
1739
2322
|
`PanoContextMenu` child), call `createPanoContextMenuPresets({ reset,
|
|
1740
2323
|
toggleFullscreen, isFullscreen })` and use `.resetView` / `.fullscreen` /
|
|
1741
2324
|
`.defaults`.
|
|
1742
2325
|
|
|
1743
|
-
Concrete items may use an `icon` React node or an `image` URL
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2326
|
+
Concrete items may use an `icon` React node or an `image` URL (optional
|
|
2327
|
+
`imageAlt`). Items may also set `disabled`, `hidden`, `className`, and `style`.
|
|
2328
|
+
`onSelect` receives `position`, `view`, `nativeEvent`, and `close()`. Separators
|
|
2329
|
+
always come from the `"separator"` preset.
|
|
2330
|
+
|
|
2331
|
+
When `items` is provided, `prepend` / `append` are ignored. `children` is a
|
|
2332
|
+
full custom render: `(props: { event, view, close }) => ReactNode`; it takes
|
|
2333
|
+
precedence over `items` while open/close/positioning stay with the component.
|
|
2334
|
+
`className`, `style`, `itemClassName`, and `separatorClassName` are also accepted.
|
|
2335
|
+
|
|
2336
|
+
Appearance fields and defaults: `background` `#161616`, `color` `#f4f4f4`,
|
|
2337
|
+
`border` `"1px solid #2e2e2e"`, `borderRadius` `8`,
|
|
2338
|
+
`shadow` `"0 8px 24px rgba(0,0,0,0.45)"`, `opacity` `1` (background only,
|
|
2339
|
+
clamped to `[0.4, 1]`; labels and icons stay opaque), `fontSize` `13`,
|
|
2340
|
+
`minWidth` `180`, `padding` `4`, `iconSize` `16`, `itemPadding` `"6px 10px"`,
|
|
2341
|
+
`itemHoverBackground` `#2a2a2a`, `itemDisabledColor` `#777777`,
|
|
2342
|
+
`separatorColor` `#333333`, `separatorMargin` `"4px 6px"`. For an explicit
|
|
2343
|
+
translucent fill without using `opacity`, you can still pass
|
|
2344
|
+
`background: "rgba(...)"`.
|
|
1749
2345
|
|
|
1750
2346
|
For a fully custom DOM menu, set `contextMenu={false}` and render
|
|
1751
2347
|
`PanoContextMenu` as a child. Do not mount both the default menu and a child
|
|
@@ -1754,15 +2350,34 @@ For a fully custom DOM menu, set `contextMenu={false}` and render
|
|
|
1754
2350
|
|
|
1755
2351
|
## Shared hotspot contract and saved definitions
|
|
1756
2352
|
|
|
1757
|
-
Every hotspot has an `id`, optional
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2353
|
+
Every hotspot has an `id`, optional shared fields, and semantic interaction
|
|
2354
|
+
callbacks. Point hotspots additionally use a controlled `position`, angular
|
|
2355
|
+
`width`/`height`, hotspot mode, and the zoom options described below.
|
|
2356
|
+
`interactive={false}` keeps a hotspot visible while letting a drawing tool
|
|
2357
|
+
receive the panorama pointer events beneath it. `pointerEvents="none"` ignores
|
|
2358
|
+
mouse, touch, and pen so those events pass through to the panorama even when
|
|
2359
|
+
the hotspot stays keyboard-interactive. Interactive hotspots set the canvas
|
|
2360
|
+
cursor to `pointer` while hovered, or to the hotspot's `cursor` prop when that
|
|
2361
|
+
is set.
|
|
2362
|
+
|
|
2363
|
+
Shared field defaults and usage:
|
|
2364
|
+
|
|
2365
|
+
- `visible` — default `true`.
|
|
2366
|
+
- `renderOrder` — default `0` for point hotspots; `10` for polygons / polylines.
|
|
2367
|
+
- `opacity` — default `1`; applies to point-hotspot textures / iframes.
|
|
2368
|
+
- `selected` — default `false`; when `true`, draws an ~3px selection outline (common in authoring).
|
|
2369
|
+
- `interactive` — default `true`; `pointerEvents` defaults to `"auto"`.
|
|
2370
|
+
- `onHoverChange(hovered, event)` — boolean hover state, then the interaction event.
|
|
2371
|
+
- `onClick` — position and input source (`"pointer"` or `"keyboard"`).
|
|
2372
|
+
- Point-hotspot drag: with `draggable`, `onDragStart` fires at the start,
|
|
2373
|
+
`onPositionChange` reports the new position while moving (the host must write
|
|
2374
|
+
it back), and `onDragEnd` fires on release. Polygon / polyline callbacks with
|
|
2375
|
+
the same names carry vertex sets — see those sections.
|
|
2376
|
+
|
|
2377
|
+
Default angular size when `width` / `height` are omitted: `ImageHotspot` /
|
|
2378
|
+
`SequenceHotspot` `12×8`; canvas `GraphicHotspot` `10×10` (`12×8` when
|
|
2379
|
+
`kind: "svg"` loads as an image); `VideoHotspot` `16×9`; `AudioHotspot` `8×8`;
|
|
2380
|
+
`TextHotspot` `16×6`; `IframeHotspot` `18×12`.
|
|
1766
2381
|
|
|
1767
2382
|
```tsx
|
|
1768
2383
|
<TextHotspot
|
|
@@ -1773,11 +2388,10 @@ the hotspot's `cursor` prop when that is set.
|
|
|
1773
2388
|
/>
|
|
1774
2389
|
```
|
|
1775
2390
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
controlled `vertices` through `onVerticesChange`.
|
|
2391
|
+
Point hotspots can use `draggable`, `onDragStart`, `onPositionChange`, and
|
|
2392
|
+
`onDragEnd`; the host must write the reported position back to its state.
|
|
2393
|
+
Polygon and polyline hotspots report controlled `vertices` through
|
|
2394
|
+
`onVerticesChange`.
|
|
1781
2395
|
|
|
1782
2396
|
Every hotspot can show a DOM tooltip at its anchor. Pass a string or
|
|
1783
2397
|
`{ text?, image?, imageAlt? }` as `tooltip`. The default `tooltipTrigger` is
|
|
@@ -1855,7 +2469,10 @@ const hotspots: HotspotDefinition[] = [
|
|
|
1855
2469
|
|
|
1856
2470
|
Definitions intentionally contain data only. Render them with a switch in the
|
|
1857
2471
|
host, where application-specific click actions, controlled media state, and
|
|
1858
|
-
error reporting belong.
|
|
2472
|
+
error reporting belong. Project JSON `label` is not a component prop;
|
|
2473
|
+
`ProjectScenes` uses `hotspot.label || hotspot.id` as the accessible name
|
|
2474
|
+
(`ariaLabel`). `selected` exists only on runtime props and is not part of the
|
|
2475
|
+
saved definition.
|
|
1859
2476
|
|
|
1860
2477
|
## ImageHotspot
|
|
1861
2478
|
|
|
@@ -1885,15 +2502,19 @@ move a selected hotspot. Clickable hotspots need `ariaLabel`: PanoViewer creates
|
|
|
1885
2502
|
an internal semantic control for Tab, Enter, and Space activation, with a
|
|
1886
2503
|
visible WebGL focus outline.
|
|
1887
2504
|
|
|
1888
|
-
`ImageHotspot` calls `onLoad(texture)` and `onError(error)`.
|
|
2505
|
+
`ImageHotspot` calls `onLoad(texture)` and `onError(error)`. When size is
|
|
2506
|
+
omitted, defaults are `width={12}` and `height={8}`.
|
|
1889
2507
|
|
|
1890
2508
|
## GraphicHotspot
|
|
1891
2509
|
|
|
1892
2510
|
`GraphicHotspot` accepts built-in `circle`, `triangle`, `diamond`, `star`,
|
|
1893
2511
|
`arrow`, `rectangle`, and `ring` graphics, an SVG URL, or safe SVG path data
|
|
1894
2512
|
with an explicit viewBox. Built-in graphics support `fill`, `stroke`, and
|
|
1895
|
-
`strokeWidth`; rectangles support a relative `cornerRadius` from `0` to `0.5
|
|
1896
|
-
|
|
2513
|
+
`strokeWidth`; rectangles support a relative `cornerRadius` from `0` to `0.5`;
|
|
2514
|
+
rings support `innerRadius` (fraction of the outer radius, default `0.58`).
|
|
2515
|
+
Canvas `strokeWidth` is line width on the **512** texture (default `8`), not
|
|
2516
|
+
the CSS-pixel stroke used by polygons. `kind: "path"` needs `path` and
|
|
2517
|
+
`viewBox: [x, y, width, height]`.
|
|
1897
2518
|
|
|
1898
2519
|
```tsx
|
|
1899
2520
|
import { GraphicHotspot } from "@ericchen1990/pano-view";
|
|
@@ -1943,7 +2564,8 @@ relative proportion, not a texture-pixel measurement.
|
|
|
1943
2564
|
`GraphicHotspot` uses the same `onLoad(texture)` and `onError(error)` callbacks
|
|
1944
2565
|
as `ImageHotspot`. Built-in paths are rasterized locally; only an SVG URL
|
|
1945
2566
|
or SVG `path` data plus an explicit `viewBox` is accepted, never arbitrary SVG
|
|
1946
|
-
markup.
|
|
2567
|
+
markup. Canvas graphics default to angular size `10×10`; `kind: "svg"` image
|
|
2568
|
+
loads default to `12×8`.
|
|
1947
2569
|
|
|
1948
2570
|
## Hotspot mode and zoom behaviour
|
|
1949
2571
|
|
|
@@ -2018,6 +2640,13 @@ stroke. Dragging moves all vertices together while keeping yaw normalized and
|
|
|
2018
2640
|
pitch clamped. Individual vertex editing is provided by the authoring
|
|
2019
2641
|
workflow, not the runtime hotspot component.
|
|
2020
2642
|
|
|
2643
|
+
Defaults: `fill` `#df6b42`, `fillOpacity` `0.34`, `stroke` `#f5fbfc`,
|
|
2644
|
+
`strokeWidth` `2`, `strokeOpacity` `1`, `renderOrder` `10`. `onDragStart` /
|
|
2645
|
+
`onDragEnd` / `onVerticesChange` carry vertex events (`vertices`,
|
|
2646
|
+
`startVertices`, `position`, `startPosition`), not point-hotspot drag events.
|
|
2647
|
+
`onInvalid` codes are `too_few_vertices`, `self_intersection`,
|
|
2648
|
+
`contains_pole`, and `outside_hemisphere`.
|
|
2649
|
+
|
|
2021
2650
|
Set `fillOpacity={0}` for a closed outline-only polygon. The fill and outline
|
|
2022
2651
|
share the same spherical edge sampling so the outline follows the fill boundary.
|
|
2023
2652
|
|
|
@@ -2046,7 +2675,10 @@ import { PolylineHotspot } from "@ericchen1990/pano-view";
|
|
|
2046
2675
|
/>;
|
|
2047
2676
|
```
|
|
2048
2677
|
|
|
2049
|
-
`onInvalid` reports the only invalid runtime shape: fewer than two vertices
|
|
2678
|
+
`onInvalid` reports the only invalid runtime shape: fewer than two vertices
|
|
2679
|
+
(`code: "too_few_vertices"`). Defaults: `stroke` `#f5fbfc`, `strokeWidth` `2`,
|
|
2680
|
+
`strokeOpacity` `1`, `renderOrder` `10`. Drag callbacks likewise carry a
|
|
2681
|
+
vertex set (`PolylineVerticesChangeEvent`).
|
|
2050
2682
|
|
|
2051
2683
|
## SequenceHotspot
|
|
2052
2684
|
|
|
@@ -2054,7 +2686,8 @@ import { PolylineHotspot } from "@ericchen1990/pano-view";
|
|
|
2054
2686
|
frames in a vertical or horizontal strip. This matches the common krpano
|
|
2055
2687
|
animated-hotspot format. Give `width` and `height` the aspect ratio of a single
|
|
2056
2688
|
frame, rather than the full strip. `playing` is controlled by the host; `fps`
|
|
2057
|
-
defaults to `12` and `
|
|
2689
|
+
defaults to `12`, `loop` to `true`, and `frameDirection` to `"vertical"`.
|
|
2690
|
+
Omitted size defaults to `12×8`.
|
|
2058
2691
|
|
|
2059
2692
|
```tsx
|
|
2060
2693
|
import { SequenceHotspot } from "@ericchen1990/pano-view";
|
|
@@ -2076,7 +2709,10 @@ import { SequenceHotspot } from "@ericchen1990/pano-view";
|
|
|
2076
2709
|
/>;
|
|
2077
2710
|
```
|
|
2078
2711
|
|
|
2079
|
-
Sequence loading uses `onLoadProgress`
|
|
2712
|
+
Sequence loading uses `onLoadProgress` (`{ id, total, loaded, failed }`) and
|
|
2713
|
+
`onError` (`{ id, src, error }`). Also available: `onFrameChange`
|
|
2714
|
+
(`{ id, index, src }`) and `onPlaybackStateChange`
|
|
2715
|
+
(`"playing" | "paused" | "ended"`, no `"blocked"`). The component does not
|
|
2080
2716
|
change `playing` by itself: update that prop in response to your UI, click
|
|
2081
2717
|
handler, `onEnded`, or playback error.
|
|
2082
2718
|
|
|
@@ -2088,7 +2724,9 @@ state. On source change or unmount, the element is paused and its texture is
|
|
|
2088
2724
|
released. `playsInline` defaults to `true` and uses the same inline-playback
|
|
2089
2725
|
attributes as `PanoVideo`. The element is mounted inside the viewer, hidden,
|
|
2090
2726
|
so mobile browsers keep it in the page. `muted` defaults to `true`, `loop` to
|
|
2091
|
-
`false`,
|
|
2727
|
+
`false`, `preload` to `"metadata"` (also `"none"` | `"auto"`), and `volume` to
|
|
2728
|
+
`1` (0–1). Omitted size defaults to `16×9`. Cross-origin media may set
|
|
2729
|
+
`crossOrigin` (`""` | `"anonymous"` | `"use-credentials"`).
|
|
2092
2730
|
|
|
2093
2731
|
```tsx
|
|
2094
2732
|
import { VideoHotspot } from "@ericchen1990/pano-view";
|
|
@@ -2117,9 +2755,10 @@ Browsers can reject unmuted or otherwise non-gesture playback. In that case
|
|
|
2117
2755
|
the browser error; retain control of `playing` in the host and offer an
|
|
2118
2756
|
explicit user action.
|
|
2119
2757
|
|
|
2120
|
-
Video reports media and poster failures through `onError
|
|
2121
|
-
`SequenceHotspot`, it does not change
|
|
2122
|
-
changes, unmounts, and React StrictMode
|
|
2758
|
+
Video reports media and poster failures through `onError`, including
|
|
2759
|
+
`source: "poster" | "video"`. Like `SequenceHotspot`, it does not change
|
|
2760
|
+
`playing` by itself. This keeps source changes, unmounts, and React StrictMode
|
|
2761
|
+
lifecycles deterministic.
|
|
2123
2762
|
|
|
2124
2763
|
## AudioHotspot
|
|
2125
2764
|
|
|
@@ -2170,19 +2809,26 @@ import { AudioHotspot } from "@ericchen1990/pano-view";
|
|
|
2170
2809
|
```
|
|
2171
2810
|
|
|
2172
2811
|
Like the other media hotspots, `AudioHotspot` does not change `playing` by
|
|
2173
|
-
itself.
|
|
2812
|
+
itself. Omitted marker size defaults to `8×8`. `crossOrigin` is also accepted;
|
|
2813
|
+
load failures go to `onError({ id, error })`, distinct from `onPlaybackError`.
|
|
2174
2814
|
|
|
2175
2815
|
## TextHotspot
|
|
2176
2816
|
|
|
2177
2817
|
`TextHotspot` rasterizes plain text onto a canvas texture. It does not accept
|
|
2178
2818
|
HTML or Markdown. `\n` is a hard line break; `whiteSpace="normal"` (default)
|
|
2179
|
-
wraps words to the hotspot's angular width
|
|
2180
|
-
the other point hotspots
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
`
|
|
2185
|
-
|
|
2819
|
+
wraps words to the hotspot's angular width; `"nowrap"` is also accepted.
|
|
2820
|
+
Dimensions stay in degrees like the other point hotspots; the default panel is
|
|
2821
|
+
`16×6`. Typography uses the same names as CSS (`fontFamily`, `fontSize`,
|
|
2822
|
+
`fontWeight`, `fontStyle`) but is painted onto the canvas, not applied as DOM
|
|
2823
|
+
style. `fontSize` is canvas pixels (default 96) and does not scale with panel
|
|
2824
|
+
height. `fontWeight` defaults to `600` and `fontStyle` to `"normal"`. `align`
|
|
2825
|
+
defaults to `"center"` (`left` | `center` | `right`); `verticalAlign` defaults
|
|
2826
|
+
to `"middle"` (`top` | `middle` | `bottom`). `color` defaults to `#f8fafc`;
|
|
2827
|
+
`background` defaults to `#111827` (empty string skips the panel fill);
|
|
2828
|
+
`backgroundOpacity` defaults to `0.72`. `padding` defaults to `0.08` and
|
|
2829
|
+
`borderRadius` to `0.08` (fractions of the shorter side, `0`–`0.5`).
|
|
2830
|
+
`lineHeight` defaults to `1.25` (multiple of font size). `stroke` /
|
|
2831
|
+
`strokeWidth` draw an outline when `strokeWidth > 0` (default width `0`).
|
|
2186
2832
|
|
|
2187
2833
|
```tsx
|
|
2188
2834
|
import { TextHotspot } from "@ericchen1990/pano-view";
|
|
@@ -2241,8 +2887,10 @@ interactive and blocks dragging through the iframe. A hotspot
|
|
|
2241
2887
|
`pointerEvents="none"` also forces the iframe overlay not to capture pointer
|
|
2242
2888
|
events. `sandbox` defaults to
|
|
2243
2889
|
`allow-scripts allow-popups allow-forms` without `allow-same-origin`.
|
|
2244
|
-
`referrerPolicy` defaults to `strict-origin-when-cross-origin`.
|
|
2245
|
-
|
|
2890
|
+
`referrerPolicy` defaults to `strict-origin-when-cross-origin`. Also accepted:
|
|
2891
|
+
`title` (falls back to `ariaLabel`), `allow`, `loading` (`"eager"` | `"lazy"`),
|
|
2892
|
+
`background`, and `onLoad` / `onError`. Omitted size defaults to `18×12`. Prefer
|
|
2893
|
+
a same-origin document; many third-party sites send `X-Frame-Options` and will
|
|
2246
2894
|
not load.
|
|
2247
2895
|
|
|
2248
2896
|
## Next.js and SSR
|