@ericchen1990/pano-view 3.9.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 +99 -55
- package/README.zh-CN.md +76 -42
- package/dist/index.cjs +1476 -768
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +75 -4
- package/dist/index.d.ts +75 -4
- package/dist/index.mjs +1241 -528
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +8 -0
- 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
|
@@ -230,17 +230,18 @@ Store this content structure in `project.json`; store media as separate files. P
|
|
|
230
230
|
|
|
231
231
|
```tsx
|
|
232
232
|
import { useState } from "react";
|
|
233
|
-
import { PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
233
|
+
import { PanoOverlay, PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
234
234
|
import { ProjectPopupDialog } from "@ericchen1990/pano-view/ui";
|
|
235
235
|
import "@ericchen1990/pano-view/ui/styles.css";
|
|
236
236
|
|
|
237
237
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
238
238
|
const [popup, setPopup] = useState<ProjectPopupDefinition | null>(null);
|
|
239
|
-
return <PanoViewer
|
|
240
|
-
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
241
|
-
)}>
|
|
239
|
+
return <PanoViewer>
|
|
242
240
|
<ProjectScenes project={project} onPopupRequest={({ popup }) => setPopup(popup)}
|
|
243
241
|
onActiveSceneIdChange={() => setPopup(null)} />
|
|
242
|
+
<PanoOverlay>
|
|
243
|
+
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
244
|
+
</PanoOverlay>
|
|
244
245
|
</PanoViewer>;
|
|
245
246
|
}
|
|
246
247
|
```
|
|
@@ -299,32 +300,35 @@ The document stores saved defaults (`image`, `spots`, `sector`, `presentation`,
|
|
|
299
300
|
|
|
300
301
|
#### Mounting `PanoMapOverlay`
|
|
301
302
|
|
|
302
|
-
Mount it
|
|
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.
|
|
303
304
|
|
|
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.
|
|
305
306
|
|
|
306
307
|
```tsx
|
|
307
308
|
import { useState } from "react";
|
|
308
309
|
import {
|
|
309
|
-
PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
310
|
+
PanoOverlay, PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
310
311
|
} from "@ericchen1990/pano-view";
|
|
311
312
|
import "@ericchen1990/pano-view/styles.css";
|
|
312
313
|
|
|
313
314
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
314
315
|
const [activeSceneId, setActiveSceneId] = useState(project.initialSceneId);
|
|
315
316
|
const map = project.map;
|
|
316
|
-
return <PanoViewer style={{ height: 600 }}
|
|
317
|
-
<PanoMapOverlay
|
|
318
|
-
map={map}
|
|
319
|
-
activeSceneId={activeSceneId}
|
|
320
|
-
onSelectScene={setActiveSceneId}
|
|
321
|
-
/>
|
|
322
|
-
) : undefined}>
|
|
317
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
323
318
|
<ProjectScenes
|
|
324
319
|
project={project}
|
|
325
320
|
activeSceneId={activeSceneId}
|
|
326
321
|
onActiveSceneIdChange={setActiveSceneId}
|
|
327
322
|
/>
|
|
323
|
+
{map ? (
|
|
324
|
+
<PanoOverlay>
|
|
325
|
+
<PanoMapOverlay
|
|
326
|
+
map={map}
|
|
327
|
+
activeSceneId={activeSceneId}
|
|
328
|
+
onSelectScene={setActiveSceneId}
|
|
329
|
+
/>
|
|
330
|
+
</PanoOverlay>
|
|
331
|
+
) : null}
|
|
328
332
|
</PanoViewer>;
|
|
329
333
|
}
|
|
330
334
|
```
|
|
@@ -679,14 +683,15 @@ Presets and hotspot rendering are in [Scenes](#scenes).
|
|
|
679
683
|
### Events and chrome
|
|
680
684
|
|
|
681
685
|
- [`PanoEvents`](#panoevents) — viewer-level lifecycle and interaction callbacks
|
|
686
|
+
- [`PanoOverlay`](#dom-overlays-and-dialogs) — screen-plane DOM wrapper for fullscreen-safe UI
|
|
682
687
|
- [`PanoContextMenu`](#panocontextmenu) — right-click reset / fullscreen menu
|
|
683
|
-
- [`PanoSceneMenu`](#panoscenemenu) — scrollable thumbnail scene picker for `
|
|
688
|
+
- [`PanoSceneMenu`](#panoscenemenu) — scrollable thumbnail scene picker for `PanoOverlay`
|
|
684
689
|
- [`PanoVideoControls`](#panovideo) — playback chrome for `PanoVideo` (mounted by default)
|
|
685
690
|
|
|
686
691
|
### Floor plan
|
|
687
692
|
|
|
688
693
|
- [`PanoMap`](#floor-plan-maps) — floor image, scene spots, and the yaw-linked sector
|
|
689
|
-
- [`PanoMapOverlay`](#floor-plan-maps) — docked panel or dialog launcher for `
|
|
694
|
+
- [`PanoMapOverlay`](#floor-plan-maps) — docked panel or dialog launcher for `PanoOverlay`
|
|
690
695
|
|
|
691
696
|
### Hotspots
|
|
692
697
|
|
|
@@ -711,7 +716,25 @@ Presets and hotspot rendering are in [Scenes](#scenes).
|
|
|
711
716
|
|
|
712
717
|
## DOM overlays and dialogs
|
|
713
718
|
|
|
714
|
-
|
|
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.
|
|
720
|
+
|
|
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.
|
|
715
738
|
|
|
716
739
|
### Default dialog
|
|
717
740
|
|
|
@@ -798,7 +821,7 @@ which use their spherical center. Hotspot ids must be unique within a viewer.
|
|
|
798
821
|
|
|
799
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`.
|
|
800
823
|
|
|
801
|
-
**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.
|
|
802
825
|
|
|
803
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`.
|
|
804
827
|
|
|
@@ -814,7 +837,7 @@ which use their spherical center. Hotspot ids must be unique within a viewer.
|
|
|
814
837
|
- `cursors` — `true` / omit uses grab / grabbing / pointer / move; `false` leaves the canvas cursor unchanged; an object overrides those four states.
|
|
815
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).
|
|
816
839
|
- `mediaActivation` — enabled by default when a mounted child intends to play; `false` disables automatic activation.
|
|
817
|
-
- `renderOverlay` — DOM UI outside the Three.js tree and inside fullscreen; the callback receives `portalContainer`, `size`, `visibleRect`, and `isFullscreen`.
|
|
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.
|
|
818
841
|
- `onViewChange` — `{ yaw, pitch, fov }` in degrees.
|
|
819
842
|
- `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` — panorama pointer events with `position` (yaw / pitch), `nativeEvent`, `button`, `buttons`, `clientX`, `clientY`, and optional `pointerId`.
|
|
820
843
|
- Other standard `div` props (`className`, `style`, `id`, …) are accepted; conflicting `onChange` and `contextMenu` are omitted from the type.
|
|
@@ -913,25 +936,29 @@ function SceneExample() {
|
|
|
913
936
|
### WebVR
|
|
914
937
|
|
|
915
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).
|
|
916
941
|
|
|
917
942
|
```tsx
|
|
943
|
+
import { useSyncExternalStore } from "react";
|
|
918
944
|
import { PanoViewer, Sphere, WebVR, usePanoViewer } from "@ericchen1990/pano-view";
|
|
919
945
|
|
|
920
946
|
function VRExample() {
|
|
921
947
|
const viewer = usePanoViewer();
|
|
948
|
+
const vr = useSyncExternalStore(
|
|
949
|
+
viewer.subscribeVR,
|
|
950
|
+
viewer.getVRState,
|
|
951
|
+
viewer.getVRState,
|
|
952
|
+
);
|
|
922
953
|
|
|
923
954
|
return (
|
|
924
955
|
<>
|
|
925
956
|
<button
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
await viewer.enterVR();
|
|
929
|
-
}}
|
|
957
|
+
disabled={!vr.available}
|
|
958
|
+
onClick={() => void (vr.enabled ? viewer.exitVR() : viewer.enterVR())}
|
|
930
959
|
>
|
|
931
|
-
Enter VR
|
|
960
|
+
{vr.enabled ? "Exit VR" : "Enter VR"}
|
|
932
961
|
</button>
|
|
933
|
-
<button onClick={() => void viewer.exitVR()}>Exit VR</button>
|
|
934
|
-
<button onClick={() => console.log(viewer.getVRMode())}>Mode?</button>
|
|
935
962
|
<PanoViewer ref={viewer.ref} style={{ height: 560 }}>
|
|
936
963
|
<WebVR />
|
|
937
964
|
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
@@ -1470,8 +1497,8 @@ Scene `viewLimits` are covered in the FAQ; `yawOffset` matches the source compon
|
|
|
1470
1497
|
## PanoSceneMenu
|
|
1471
1498
|
|
|
1472
1499
|
`PanoSceneMenu` is a scrollable thumbnail strip for switching scenes. Mount it
|
|
1473
|
-
|
|
1474
|
-
|
|
1500
|
+
inside `PanoOverlay` so it stays inside the viewer (including fullscreen).
|
|
1501
|
+
Import `@ericchen1990/pano-view/styles.css` for the default layout.
|
|
1475
1502
|
|
|
1476
1503
|
The menu is host-driven: you pass `items`, `activeId`, and `onSelect`. It does
|
|
1477
1504
|
not read `Scenes` state or scene `previewUrl`. Cube `previewUrl` values are
|
|
@@ -1485,6 +1512,7 @@ Keep one controlled scene id and share it with `Scenes`:
|
|
|
1485
1512
|
```tsx
|
|
1486
1513
|
import { useState } from "react";
|
|
1487
1514
|
import {
|
|
1515
|
+
PanoOverlay,
|
|
1488
1516
|
PanoSceneMenu,
|
|
1489
1517
|
PanoViewer,
|
|
1490
1518
|
Scenes,
|
|
@@ -1509,21 +1537,19 @@ export function Tour() {
|
|
|
1509
1537
|
const [activeSceneId, setActiveSceneId] = useState("lobby");
|
|
1510
1538
|
|
|
1511
1539
|
return (
|
|
1512
|
-
<PanoViewer
|
|
1513
|
-
style={{ height: 560 }}
|
|
1514
|
-
renderOverlay={() => (
|
|
1515
|
-
<PanoSceneMenu
|
|
1516
|
-
activeId={activeSceneId}
|
|
1517
|
-
items={menuItems}
|
|
1518
|
-
onSelect={setActiveSceneId}
|
|
1519
|
-
/>
|
|
1520
|
-
)}
|
|
1521
|
-
>
|
|
1540
|
+
<PanoViewer style={{ height: 560 }}>
|
|
1522
1541
|
<Scenes
|
|
1523
1542
|
scenes={scenes}
|
|
1524
1543
|
activeSceneId={activeSceneId}
|
|
1525
1544
|
onActiveSceneIdChange={setActiveSceneId}
|
|
1526
1545
|
/>
|
|
1546
|
+
<PanoOverlay>
|
|
1547
|
+
<PanoSceneMenu
|
|
1548
|
+
activeId={activeSceneId}
|
|
1549
|
+
items={menuItems}
|
|
1550
|
+
onSelect={setActiveSceneId}
|
|
1551
|
+
/>
|
|
1552
|
+
</PanoOverlay>
|
|
1527
1553
|
</PanoViewer>
|
|
1528
1554
|
);
|
|
1529
1555
|
}
|
|
@@ -1908,8 +1934,9 @@ These are props on `Gyro` itself. `PanoEvents` `onGyro*` callbacks subscribe to
|
|
|
1908
1934
|
`WebVR` adds immersive viewing to the nearest `PanoViewer`. It prefers an
|
|
1909
1935
|
`immersive-vr` WebXR session on supported headsets. On phones without WebXR it
|
|
1910
1936
|
uses a stereoscopic split-screen view with device orientation, and on desktop
|
|
1911
|
-
it can provide a mouse-driven simulated VR preview.
|
|
1912
|
-
|
|
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).
|
|
1913
1940
|
|
|
1914
1941
|
```tsx
|
|
1915
1942
|
import { useRef } from "react";
|
|
@@ -1939,19 +1966,28 @@ export function WebVRExample() {
|
|
|
1939
1966
|
```
|
|
1940
1967
|
|
|
1941
1968
|
The handle exposes `enterVR()`, `exitVR()`, `toggleVR()`, `isAvailable()`,
|
|
1942
|
-
`isEnabled()`, `getMode()`,
|
|
1943
|
-
|
|
1944
|
-
`"cardboard-
|
|
1945
|
-
`{ fov, ipdMm, k1, k2 }`
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
`
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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.
|
|
1955
1991
|
|
|
1956
1992
|
WebXR requires HTTPS and a user gesture. Mobile sensor access also requires
|
|
1957
1993
|
HTTPS and may require an explicit iOS permission prompt. Embedded viewers need
|
|
@@ -1962,19 +1998,27 @@ interactive through the default WebXR controller rays.
|
|
|
1962
1998
|
|
|
1963
1999
|
### Props
|
|
1964
2000
|
|
|
1965
|
-
- `chrome` — default `true` mounts
|
|
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.
|
|
1966
2002
|
- `mobileVr` — default `true`.
|
|
1967
2003
|
- `desktopSupport` — default `false` (independent of the same-named `Gyro` prop).
|
|
1968
2004
|
- `fakeSupport` — default `true` for mouse-driven desktop simulation.
|
|
1969
2005
|
- `screensize` — `"auto"` (factory / omitted storage default) or inches; numbers are clamped to **3–20** with one decimal place.
|
|
1970
|
-
- `
|
|
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"`).
|
|
1971
2015
|
- `cursor` — default `true`; `false` hides the center reticle.
|
|
1972
2016
|
- `cursorDwellMs` — default `1500`; `0` disables auto-click.
|
|
1973
2017
|
- `wakelock` / `mousePointerLock` — both default to `true`.
|
|
1974
2018
|
- `onAvailable` / `onUnavailable` — VR capability changes.
|
|
1975
2019
|
- `onEnterVR` / `onExitVR` — mode `"webxr" | "mobilevr" | "fake"`.
|
|
1976
2020
|
- `onDenied` — permission or session denial, optional `error`.
|
|
1977
|
-
- `onUnknownDevice` — when
|
|
2021
|
+
- `onUnknownDevice` — when `"auto"` screensize cannot be resolved from the DPDB (disabled URL, no match, or fetch failure).
|
|
1978
2022
|
|
|
1979
2023
|
## Mouse, touch, and keyboard controls
|
|
1980
2024
|
|
package/README.zh-CN.md
CHANGED
|
@@ -226,17 +226,18 @@ export function Tour() {
|
|
|
226
226
|
|
|
227
227
|
```tsx
|
|
228
228
|
import { useState } from "react";
|
|
229
|
-
import { PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
229
|
+
import { PanoOverlay, PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
|
|
230
230
|
import { ProjectPopupDialog } from "@ericchen1990/pano-view/ui";
|
|
231
231
|
import "@ericchen1990/pano-view/ui/styles.css";
|
|
232
232
|
|
|
233
233
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
234
234
|
const [popup, setPopup] = useState<ProjectPopupDefinition | null>(null);
|
|
235
|
-
return <PanoViewer
|
|
236
|
-
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
237
|
-
)}>
|
|
235
|
+
return <PanoViewer>
|
|
238
236
|
<ProjectScenes project={project} onPopupRequest={({ popup }) => setPopup(popup)}
|
|
239
237
|
onActiveSceneIdChange={() => setPopup(null)} />
|
|
238
|
+
<PanoOverlay>
|
|
239
|
+
<ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
|
|
240
|
+
</PanoOverlay>
|
|
240
241
|
</PanoViewer>;
|
|
241
242
|
}
|
|
242
243
|
```
|
|
@@ -295,32 +296,35 @@ export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
|
295
296
|
|
|
296
297
|
#### 挂载 `PanoMapOverlay`
|
|
297
298
|
|
|
298
|
-
在 `
|
|
299
|
+
在 `PanoOverlay` 中挂载,并把交给 `ProjectScenes` 的同一个 `activeSceneId` 传进去。内置 `pano-map-*` 类名来自 `@ericchen1990/pano-view/styles.css`。overlay 从查看器读取 yaw 和视场角,不会自己切换场景:在 `onSelectScene` 里设置当前场景。相机状态来自查看器之外时传入 `view`;查看器外又不传 `view` 时,图面停在 yaw `0`、pitch `0`、fov `75`。`PanoMap` 只负责图面,由宿主自己安排外框。
|
|
299
300
|
|
|
300
301
|
`PanoMap` 还可受控选中与收起:`selectedSpotId` / `onSelectedSpotIdChange`(省略时内部选中,默认 `null`);`collapsed` / `onCollapsedChange`(非受控初始为展开);`renderChrome` 在图面之上渲染宿主控件。`showZoomControls` / `showCollapseControl` 与 overlay 相同,也可直接传给 `PanoMap`。`className` / `style` 挂在表面根节点上。
|
|
301
302
|
|
|
302
303
|
```tsx
|
|
303
304
|
import { useState } from "react";
|
|
304
305
|
import {
|
|
305
|
-
PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
306
|
+
PanoOverlay, PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
|
|
306
307
|
} from "@ericchen1990/pano-view";
|
|
307
308
|
import "@ericchen1990/pano-view/styles.css";
|
|
308
309
|
|
|
309
310
|
export function Tour({ project }: { project: LoadedPanoProject }) {
|
|
310
311
|
const [activeSceneId, setActiveSceneId] = useState(project.initialSceneId);
|
|
311
312
|
const map = project.map;
|
|
312
|
-
return <PanoViewer style={{ height: 600 }}
|
|
313
|
-
<PanoMapOverlay
|
|
314
|
-
map={map}
|
|
315
|
-
activeSceneId={activeSceneId}
|
|
316
|
-
onSelectScene={setActiveSceneId}
|
|
317
|
-
/>
|
|
318
|
-
) : undefined}>
|
|
313
|
+
return <PanoViewer style={{ height: 600 }}>
|
|
319
314
|
<ProjectScenes
|
|
320
315
|
project={project}
|
|
321
316
|
activeSceneId={activeSceneId}
|
|
322
317
|
onActiveSceneIdChange={setActiveSceneId}
|
|
323
318
|
/>
|
|
319
|
+
{map ? (
|
|
320
|
+
<PanoOverlay>
|
|
321
|
+
<PanoMapOverlay
|
|
322
|
+
map={map}
|
|
323
|
+
activeSceneId={activeSceneId}
|
|
324
|
+
onSelectScene={setActiveSceneId}
|
|
325
|
+
/>
|
|
326
|
+
</PanoOverlay>
|
|
327
|
+
) : null}
|
|
324
328
|
</PanoViewer>;
|
|
325
329
|
}
|
|
326
330
|
```
|
|
@@ -652,14 +656,15 @@ export function Tour() {
|
|
|
652
656
|
### 事件与界面元素
|
|
653
657
|
|
|
654
658
|
- [`PanoEvents`](#panoevents) — 查看器级生命周期与交互回调
|
|
659
|
+
- [`PanoOverlay`](#dom-overlay-与弹窗) — 查看器平面上的 DOM 包裹,全屏时仍可见
|
|
655
660
|
- [`PanoContextMenu`](#panocontextmenu) — 右键重置 / 全屏菜单
|
|
656
|
-
- [`PanoSceneMenu`](#panoscenemenu) — 供 `
|
|
661
|
+
- [`PanoSceneMenu`](#panoscenemenu) — 供 `PanoOverlay` 使用的可滚动缩略图场景菜单
|
|
657
662
|
- [`PanoVideoControls`](#panovideo) — `PanoVideo` 的播放界面(默认挂载)
|
|
658
663
|
|
|
659
664
|
### 平面图
|
|
660
665
|
|
|
661
666
|
- [`PanoMap`](#平面图地图) — 平面图、场景点位,以及随水平角转动的扇形
|
|
662
|
-
- [`PanoMapOverlay`](#平面图地图) — 供 `
|
|
667
|
+
- [`PanoMapOverlay`](#平面图地图) — 供 `PanoOverlay` 使用的停靠面板或对话框按钮
|
|
663
668
|
|
|
664
669
|
### 热点
|
|
665
670
|
|
|
@@ -684,7 +689,25 @@ export function Tour() {
|
|
|
684
689
|
|
|
685
690
|
## DOM Overlay 与弹窗
|
|
686
691
|
|
|
687
|
-
普通 DOM
|
|
692
|
+
普通 DOM 界面优先使用 `PanoOverlay`,位于 Three.js 渲染树之外,挂在全屏根节点内的查看器平面上,因此全屏时仍可见。把它作为 `PanoViewer` 的直接子节点(或包在其下的 Fragment 里),与场景内容并列。`renderOverlay` 仍可用,适合回调式挂载:
|
|
693
|
+
|
|
694
|
+
```tsx
|
|
695
|
+
<PanoViewer style={{ height: 560 }}>
|
|
696
|
+
<Sphere src="/pano.jpg" />
|
|
697
|
+
<PanoOverlay>
|
|
698
|
+
<PanoSceneMenu items={items} activeId={id} onSelect={setId} />
|
|
699
|
+
<button
|
|
700
|
+
type="button"
|
|
701
|
+
style={{ position: "absolute", right: 16, bottom: 16, pointerEvents: "auto" }}
|
|
702
|
+
onClick={() => void viewer.toggleFullscreen()}
|
|
703
|
+
>
|
|
704
|
+
Fullscreen
|
|
705
|
+
</button>
|
|
706
|
+
</PanoOverlay>
|
|
707
|
+
</PanoViewer>
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
该层已是 `position: absolute; inset: 0; pointer-events: none`。子节点自行定位,可点击节点设置 `pointerEvents: "auto"`。同时使用时,`renderOverlay` 先渲染,其后按出现顺序渲染各个 `PanoOverlay`。`usePanoOverlay()` 必须在 `PanoOverlay`(或 `renderOverlay`)子树内调用,返回 `portalContainer`、`size`(viewer 宽高)、`visibleRect`(可见矩形)与 `isFullscreen`。`usePanoModal(open)` 在 `open === true` 时占用该 viewer 的模态锁(暂停输入与自动旋转、背景 inert、关闭右键菜单);不负责焦点陷阱。`PanoOverlayPortalProvider` 接收必填的 `container` 与 `children`,只替换后代读到的 `portalContainer`;`size` / `visibleRect` 仍是整个 viewer。字段含义、引用计数与第三方 Dialog 接线见独立的 **[弹窗使用与自定义样式指南](./docs/dialogs.zh-CN.md)**。可选 `/ui` 入口提供 `PanoDialog` 与组成组件。指南随 npm 包分发;在本仓库运行 `pnpm dev`,打开 `/overlays` 可体验示例。
|
|
688
711
|
|
|
689
712
|
### 默认弹窗
|
|
690
713
|
|
|
@@ -769,7 +792,7 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
|
|
|
769
792
|
|
|
770
793
|
**场景**(需 `<Scenes />`)— `setScene(id)`、`nextScene`、`previousScene`、`getActiveSceneId`、`getSceneIds`、`isSceneTransitioning`。`Scenes` 支持 `defaultActiveSceneId`(非受控)或 `activeSceneId`(受控)。受控模式下命令式调用会触发 `onActiveSceneIdChange`。
|
|
771
794
|
|
|
772
|
-
**WebVR**(需 `<WebVR />`)— `enterVR`、`exitVR`、`toggleVR`、`isVRAvailable`、`isVREnabled`、`getVRMode`、`requestVRPermission
|
|
795
|
+
**WebVR**(需 `<WebVR />`)— `enterVR`、`exitVR`、`toggleVR`、`isVRAvailable`、`isVREnabled`、`getVRMode`、`getVRState`、`subscribeVR`、`requestVRPermission`。用 `subscribeVR` 配合 `useSyncExternalStore` 做响应式进入 / 退出按钮;内置 chrome 不再渲染「进入 VR」。
|
|
773
796
|
|
|
774
797
|
**视频**(需 `<PanoVideo />`)— `getVideo` 返回 `PanoVideoController`,`subscribeVideo` 订阅视频控制器与播放状态变更,另有快捷方法 `playVideo`、`pauseVideo`、`toggleVideo`、`seekVideo`、`setVideoVolume`、`setVideoMuted`、`toggleVideoMuted`。
|
|
775
798
|
|
|
@@ -785,7 +808,7 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
|
|
|
785
808
|
- `cursors` — `true` / 省略使用 grab / grabbing / pointer / move;`false` 不改画布光标;对象可覆盖四个状态。
|
|
786
809
|
- `contextMenu` — `true` / 省略挂载「重置视图 + 全屏」;`false` 不挂默认菜单;对象可改外观或 `items` / `prepend` / `append`。详见 [PanoContextMenu](#panocontextmenu)。
|
|
787
810
|
- `mediaActivation` — 有播放意图的子组件时默认开启;`false` 关闭自动激活。
|
|
788
|
-
- `renderOverlay` —
|
|
811
|
+
- `renderOverlay` — 可选的回调式 DOM UI,在画布外、全屏内渲染;回调收到 `portalContainer`、`size`、`visibleRect`、`isFullscreen`。优先用子组件 `<PanoOverlay>`。
|
|
789
812
|
- `onViewChange` — 参数为 `{ yaw, pitch, fov }`(度)。
|
|
790
813
|
- `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` — 参数为全景指针事件:`position`(yaw / pitch)、`nativeEvent`、`button`、`buttons`、`clientX`、`clientY`,以及可选的 `pointerId`。
|
|
791
814
|
- 其余标准 `div` 属性(如 `className`、`style`、`id`)可原样传入;类型已去掉冲突的 `onChange` 与 `contextMenu`。
|
|
@@ -883,26 +906,30 @@ function SceneExample() {
|
|
|
883
906
|
|
|
884
907
|
### WebVR
|
|
885
908
|
|
|
886
|
-
`enterVR` 与 `requestVRPermission`
|
|
909
|
+
`enterVR` 与 `requestVRPermission` 须在用户手势中调用。通过 `getVRState` /
|
|
910
|
+
`subscribeVR` 由宿主 UI 驱动进入;会话 chrome 只显示退出 VR 与 MobileVR 设置
|
|
911
|
+
(空闲数秒后淡出)。
|
|
887
912
|
|
|
888
913
|
```tsx
|
|
914
|
+
import { useSyncExternalStore } from "react";
|
|
889
915
|
import { PanoViewer, Sphere, WebVR, usePanoViewer } from "@ericchen1990/pano-view";
|
|
890
916
|
|
|
891
917
|
function VRExample() {
|
|
892
918
|
const viewer = usePanoViewer();
|
|
919
|
+
const vr = useSyncExternalStore(
|
|
920
|
+
viewer.subscribeVR,
|
|
921
|
+
viewer.getVRState,
|
|
922
|
+
viewer.getVRState,
|
|
923
|
+
);
|
|
893
924
|
|
|
894
925
|
return (
|
|
895
926
|
<>
|
|
896
927
|
<button
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
await viewer.enterVR();
|
|
900
|
-
}}
|
|
928
|
+
disabled={!vr.available}
|
|
929
|
+
onClick={() => void (vr.enabled ? viewer.exitVR() : viewer.enterVR())}
|
|
901
930
|
>
|
|
902
|
-
进入 VR
|
|
931
|
+
{vr.enabled ? "退出 VR" : "进入 VR"}
|
|
903
932
|
</button>
|
|
904
|
-
<button onClick={() => void viewer.exitVR()}>退出 VR</button>
|
|
905
|
-
<button onClick={() => console.log(viewer.getVRMode())}>当前模式?</button>
|
|
906
933
|
<PanoViewer ref={viewer.ref} style={{ height: 560 }}>
|
|
907
934
|
<WebVR />
|
|
908
935
|
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
@@ -1348,7 +1375,7 @@ const scenes: Scene[] = [
|
|
|
1348
1375
|
|
|
1349
1376
|
## PanoSceneMenu
|
|
1350
1377
|
|
|
1351
|
-
`PanoSceneMenu` 是可滚动的缩略图场景菜单。通过 `
|
|
1378
|
+
`PanoSceneMenu` 是可滚动的缩略图场景菜单。通过 `PanoOverlay` 挂载,这样在普通布局和全屏里都会留在 viewer 内。默认布局需导入 `@ericchen1990/pano-view/styles.css`。
|
|
1352
1379
|
|
|
1353
1380
|
菜单由宿主驱动:传入 `items`、`activeId`、`onSelect`。它不读取 `Scenes` 状态,也不使用场景的 `previewUrl`。立方体场景的 `previewUrl` 通常是 1×6 预览条,不适合直接当菜单缩略图;请改用独立缩略图(转换器输出可优先用 `previews/200.webp` 这类小图)。
|
|
1354
1381
|
|
|
@@ -1359,6 +1386,7 @@ const scenes: Scene[] = [
|
|
|
1359
1386
|
```tsx
|
|
1360
1387
|
import { useState } from "react";
|
|
1361
1388
|
import {
|
|
1389
|
+
PanoOverlay,
|
|
1362
1390
|
PanoSceneMenu,
|
|
1363
1391
|
PanoViewer,
|
|
1364
1392
|
Scenes,
|
|
@@ -1383,21 +1411,19 @@ export function Tour() {
|
|
|
1383
1411
|
const [activeSceneId, setActiveSceneId] = useState("lobby");
|
|
1384
1412
|
|
|
1385
1413
|
return (
|
|
1386
|
-
<PanoViewer
|
|
1387
|
-
style={{ height: 560 }}
|
|
1388
|
-
renderOverlay={() => (
|
|
1389
|
-
<PanoSceneMenu
|
|
1390
|
-
activeId={activeSceneId}
|
|
1391
|
-
items={menuItems}
|
|
1392
|
-
onSelect={setActiveSceneId}
|
|
1393
|
-
/>
|
|
1394
|
-
)}
|
|
1395
|
-
>
|
|
1414
|
+
<PanoViewer style={{ height: 560 }}>
|
|
1396
1415
|
<Scenes
|
|
1397
1416
|
scenes={scenes}
|
|
1398
1417
|
activeSceneId={activeSceneId}
|
|
1399
1418
|
onActiveSceneIdChange={setActiveSceneId}
|
|
1400
1419
|
/>
|
|
1420
|
+
<PanoOverlay>
|
|
1421
|
+
<PanoSceneMenu
|
|
1422
|
+
activeId={activeSceneId}
|
|
1423
|
+
items={menuItems}
|
|
1424
|
+
onSelect={setActiveSceneId}
|
|
1425
|
+
/>
|
|
1426
|
+
</PanoOverlay>
|
|
1401
1427
|
</PanoViewer>
|
|
1402
1428
|
);
|
|
1403
1429
|
}
|
|
@@ -1706,7 +1732,7 @@ export function GyroExample() {
|
|
|
1706
1732
|
|
|
1707
1733
|
## WebVR
|
|
1708
1734
|
|
|
1709
|
-
`WebVR` 为最近的 `PanoViewer` 增加沉浸式观看。支持的头戴设备优先使用 `immersive-vr` WebXR 会话。无 WebXR 的手机使用设备方向的立体分屏;桌面可提供鼠标驱动的模拟 VR
|
|
1735
|
+
`WebVR` 为最近的 `PanoViewer` 增加沉浸式观看。支持的头戴设备优先使用 `immersive-vr` WebXR 会话。无 WebXR 的手机使用设备方向的立体分屏;桌面可提供鼠标驱动的模拟 VR 预览。进入 VR 由宿主通过查看器句柄触发;内置 chrome 仅在会话中显示退出 VR 与 MobileVR 设置(空闲后淡出)。
|
|
1710
1736
|
|
|
1711
1737
|
```tsx
|
|
1712
1738
|
import { useRef } from "react";
|
|
@@ -1735,25 +1761,33 @@ export function WebVRExample() {
|
|
|
1735
1761
|
}
|
|
1736
1762
|
```
|
|
1737
1763
|
|
|
1738
|
-
句柄暴露 `enterVR()`、`exitVR()`、`toggleVR()`、`isAvailable()`、`isEnabled()`、`getMode()` 与 `
|
|
1764
|
+
句柄暴露 `enterVR()`、`exitVR()`、`toggleVR()`、`isAvailable()`、`isEnabled()`、`getMode()`、`getState()`、`subscribe()`、`requestPermission()` 与 `resetSettings()`。模式为 `"webxr"`、`"mobilevr"`、`"fake"`。`profile` 接受 `"none"`、`"cardboard-v1"`、`"cardboard-v2"`、`"gear-vr"`、`"daydream"`,或自定义 `{ fov, ipdMm, k1, k2, screenToLensMm?, trayDistanceMm? }` 镜头配置。在已知物理屏幕尺寸时,MobileVR 会用瞳距与可选的托盘到镜片高度(`trayDistanceMm`;Cardboard V1 为 `35`)放置每只眼睛的光轴;`0` 或省略则保持垂直方向居中。当 `screensize` 为 `"auto"` 时,MobileVR 会加载 Cardboard Device Parameter Database(`databaseUrl`,默认 Google 公开 DPDB)估算对角线英寸;传 `""` 或 `null` 可跳过请求。无法识别时触发 `onUnknownDevice`,并继续使用镜头预设 FOV(尺寸未知前不做托盘偏移)。MobileVR 设置值会版本化并保存在当前源的 `localStorage` 中;`resetSettings()` 会清除该存储并恢复 props 或内置默认(Setup 面板也有 Reset)。VR 中中心准星显示十字;注视可交互热点时绘制顺时针填充环,经 `cursorDwellMs`(默认 1500)后触发热点点击。设 `cursorDwellMs={0}` 可保留准星但不自动点击。模拟桌面 VR(`fake` 模式)默认请求指针锁定(`mousePointerLock`),隐藏光标并用鼠标环视;若浏览器释放锁定需再次点击视图。回退会话通过 Screen Wake Lock API 或在不支持时的循环隐藏视频保持屏幕常亮(`wakelock`)。
|
|
1739
1765
|
|
|
1740
1766
|
WebXR 需要 HTTPS 与用户手势。移动端传感器访问也需要 HTTPS,iOS 可能需显式权限提示。嵌入查看器需要适当的 `xr-spatial-tracking`、`gyroscope`、`accelerometer` 权限。iframe 热点、tooltip、视频控件等 DOM 叠加层在沉浸式头戴会话中不可见;基于 mesh 的热点仍可通过默认 WebXR 控制器射线交互。
|
|
1741
1767
|
|
|
1742
1768
|
### 属性
|
|
1743
1769
|
|
|
1744
|
-
- `chrome` — 默认 `true`
|
|
1770
|
+
- `chrome` — 默认 `true` 挂载会话内退出 / 设置界面(不含进入 VR 按钮)。也可传外观对象:`background`(默认 `rgba(16, 24, 32, 0.78)`)、`color`(`#ffffff`)、`accent`(`#7dd3fc`)、`borderRadius`(`6`)、`fontSize`(`14`)、可选 `setupTitle`。会话按钮约 3 秒无操作后淡出,与查看器的指针 / 滚轮 / 键盘交互后再次显示。
|
|
1745
1771
|
- `mobileVr` — 默认 `true`。
|
|
1746
1772
|
- `desktopSupport` — 默认 `false`(与 `Gyro` 的同名 prop 独立)。
|
|
1747
1773
|
- `fakeSupport` — 默认 `true`,允许鼠标驱动的桌面模拟 VR。
|
|
1748
1774
|
- `screensize` — `"auto"`(出厂 / 省略时的存储默认)或英寸数;数字会被夹到 **3–20** 并保留一位小数。
|
|
1749
|
-
- `
|
|
1775
|
+
- `databaseUrl` — `"auto"` 屏幕尺寸时使用的 Cardboard DPDB URL。默认 `https://storage.googleapis.com/cardboard-dpdb/dpdb.json`。传 `""` 或 `null` 可关闭在线查询。
|
|
1776
|
+
- `profile` — 省略时用已存设置,出厂为 `"cardboard-v1"`。也可传 `"none"`、`"cardboard-v2"`、`"gear-vr"`、`"daydream"`,或自定义 `{ fov, ipdMm, k1, k2, screenToLensMm?, trayDistanceMm?, label?, id? }`。`trayDistanceMm` 为头显托盘底边到镜片中心的毫米距离;`0` / 省略时垂直光轴保持屏幕中心。
|
|
1777
|
+
- `absolute` — 默认 `false`。MobileVR 使用罗盘绝对朝向(对 WebXR 无效)。
|
|
1778
|
+
- `north` — 默认 `0`。启用 `absolute` 时,全景中代表北方的 yaw。
|
|
1779
|
+
- `friction` — 默认 `0`(范围 `0–0.99`)。MobileVR 陀螺仪平滑;正常观看保持 `0`。
|
|
1780
|
+
- `touchSupport` — 默认 `false`。为 `true` 时允许水平拖动修正 MobileVR 朝向(`horizontaloffset`)。默认关闭,因为镜筒托盘可能误触。
|
|
1781
|
+
- `oversampling` — 默认 `1`,夹到 **0.5–2**。仅放大 MobileVR 畸变离屏目标(视口不变)。WebXR 则在会话开始前设置 framebuffer 缩放(会话中途不可改)。
|
|
1782
|
+
- `foveation` — 可选 WebXR 固定注视点渲染,范围 **0–1**。省略则用设备默认;`0` 为全分辨率,`1` 为边缘最大降采样。
|
|
1783
|
+
- `frameRate` — 可选 WebXR 目标刷新率:`"low" | "mid" | "high"` 或具体 Hz(取最接近的支持值)。省略则保持 store 默认(`"high"`)。
|
|
1750
1784
|
- `cursor` — 默认 `true`;`false` 关闭中心准星。
|
|
1751
1785
|
- `cursorDwellMs` — 默认 `1500`;`0` 不自动点击。
|
|
1752
1786
|
- `wakelock` / `mousePointerLock` — 默认均为 `true`。
|
|
1753
1787
|
- `onAvailable` / `onUnavailable` — VR 能力变化。
|
|
1754
1788
|
- `onEnterVR` / `onExitVR` — 参数为模式 `"webxr" | "mobilevr" | "fake"`。
|
|
1755
1789
|
- `onDenied` — 权限或会话被拒绝,参数为可选 `error`。
|
|
1756
|
-
- `onUnknownDevice` —
|
|
1790
|
+
- `onUnknownDevice` — `"auto"` 屏幕尺寸无法从 DPDB 解析时(URL 关闭、无匹配或请求失败)。
|
|
1757
1791
|
|
|
1758
1792
|
## 鼠标、触控与键盘控件
|
|
1759
1793
|
|