@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.zh-CN.md CHANGED
@@ -11,6 +11,7 @@
11
11
  - [pano-converter](#pano-converter)
12
12
  - [数据来源与选型](#数据来源与选型)
13
13
  - [静态项目与 JSON 数据](#静态项目与-json-数据)
14
+ - [平面图地图](#平面图地图)
14
15
  - [常见问题与故障排查](#常见问题与故障排查)
15
16
  - [兼容 krpano 的瓦片输出](#兼容-krpano-的瓦片输出)
16
17
  - [安装](#安装)
@@ -22,6 +23,7 @@
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)
@@ -195,7 +197,9 @@ export function Tour() {
195
197
 
196
198
  `ProjectScenes` 放在 `PanoViewer` 内,复用全部九类现有热点。`action: { type: "scene", sceneId }` 点击后切换场景;没有 action 的音频、视频、序列热点点击切换播放。离开场景时卸载媒体,再次进入恢复 JSON 初始播放配置。浏览器仍可能阻止自动播放。
197
199
 
198
- 配置了 `initialView` 的场景在 `onSceneReady` 时应用视角,缺省字段为 yaw 0、pitch 0、fov 75;未配置时保持当前视角。过渡期间热点隐藏。使用 `activeSceneId` / `onActiveSceneIdChange` 可控制当前场景。`onHotspotClick(event)` 中调用 `event.preventDefault()` 可取消默认动作;`onHotspotError` 提供场景、热点和原始错误;媒体的原始错误可能是带有 `error` 字段的事件。`renderHotspot(hotspot, context)` 返回 `undefined` 使用默认渲染,返回 `null` 隐藏;context 包含 `sceneId`、`playing`、`onClick`、`togglePlayback`、`reportError` 和 `renderDefault`。
200
+ 配置了 `initialView` 的场景在 `onSceneReady` 时应用视角,缺省字段为 yaw 0、pitch 0、fov 75;未配置时保持当前视角。过渡期间热点隐藏。使用 `activeSceneId` / `onActiveSceneIdChange` 可控制当前场景。未受控时内部状态从 `project.initialSceneId` 开始;`project` 引用变化会重置该选择。`onHotspotClick(event)` 中调用 `event.preventDefault()` 可取消默认动作;`onHotspotError` 提供 `sceneId`、热点和原始错误;媒体的原始错误可能是带有 `error` 字段的事件。`renderHotspot(hotspot, context)` 返回 `undefined` 使用默认渲染,返回 `null` 隐藏;context 包含 `sceneId`、`playing`、`onClick`、`onHoverChange`、`togglePlayback`、`reportError` 和 `renderDefault`。
201
+
202
+ `ProjectScenes` 在项目字段之外继承 `Scenes` 的过渡与缓存属性:`transition`、`onSceneReady`、`transitionWaitForDetailMs`、`onTransitionEnd`、`onTransitionError`、`maxTextureMemoryMb`、`maxConcurrentTileLoads`、`snapshotMaxPixels`(默认 `3686400`)。不能传 `scenes`、`defaultActiveSceneId` 或自行的 `renderHotspots`——场景列表来自 `project`,热点由组件渲染。
199
203
 
200
204
  ### 热点动作与共享弹窗内容
201
205
 
@@ -222,24 +226,188 @@ export function Tour() {
222
226
 
223
227
  ```tsx
224
228
  import { useState } from "react";
225
- import { PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
229
+ import { PanoOverlay, PanoViewer, ProjectScenes, type LoadedPanoProject, type ProjectPopupDefinition } from "@ericchen1990/pano-view";
226
230
  import { ProjectPopupDialog } from "@ericchen1990/pano-view/ui";
227
231
  import "@ericchen1990/pano-view/ui/styles.css";
228
232
 
229
233
  export function Tour({ project }: { project: LoadedPanoProject }) {
230
234
  const [popup, setPopup] = useState<ProjectPopupDefinition | null>(null);
231
- return <PanoViewer renderOverlay={() => (
232
- <ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
233
- )}>
235
+ return <PanoViewer>
234
236
  <ProjectScenes project={project} onPopupRequest={({ popup }) => setPopup(popup)}
235
237
  onActiveSceneIdChange={() => setPopup(null)} />
238
+ <PanoOverlay>
239
+ <ProjectPopupDialog popup={popup} onOpenChange={(open) => { if (!open) setPopup(null); }} />
240
+ </PanoOverlay>
236
241
  </PanoViewer>;
237
242
  }
238
243
  ```
239
244
 
240
245
  `ProjectPopupDialog` 接收 `popup`(null 关闭)、`onOpenChange`,以及可选 `closeLabel` 和 `onMediaError(block)`。文字按纯文本展示,音视频使用原生控件且不自动播放;关闭或切换内容时释放媒体。全屏、尺寸和焦点行为复用 `PanoDialog`。需要自定义渲染时可替换成自己的 Overlay。
241
246
 
242
- 继续使用 schemaVersion 1,旧项目仍可读取,包含新动作的项目需使用支持它们的新版查看器。`parsePanoProject(input, { draft: true })` 仅用于编辑草稿,允许空场景列表及未完成的弹窗标题/媒体;加载、保存和导出仍严格校验。宿主可将同一份文档存入数据库或映射到表,查看器不直接连接数据库。
247
+ ### 平面图地图
248
+
249
+ 项目可选字段 `map` 把场景点位放在一张平面图上。没有 `map` 的旧项目仍然有效,`schemaVersion` 仍为 1。`loadPanoProject` 返回的地图已按项目目录解析平面图和点位图标。自定义加载器也可使用 `/data` 导出的 `parsePanoMap` 与 `resolvePanoMapAssets`。
250
+
251
+ ```json
252
+ "map": {
253
+ "image": "assets/map/floor.webp",
254
+ "interaction": "zoom",
255
+ "maxScale": 4,
256
+ "iconSize": 28,
257
+ "presentation": {
258
+ "mode": "docked",
259
+ "corner": "bottom-left",
260
+ "width": 240,
261
+ "height": 180,
262
+ "offsetX": 16,
263
+ "offsetY": 16
264
+ },
265
+ "controls": { "collapse": true, "zoom": true },
266
+ "sector": {
267
+ "angle": 70,
268
+ "radius": 56,
269
+ "followFov": false,
270
+ "color": "#38bdf8",
271
+ "opacity": 0.55,
272
+ "strokeColor": "#ffffff",
273
+ "strokeWidth": 1.5,
274
+ "strokeOpacity": 0.9
275
+ },
276
+ "spots": [{
277
+ "id": "entrance-spot",
278
+ "sceneId": "entrance",
279
+ "x": 0.31,
280
+ "y": 0.5,
281
+ "heading": 20,
282
+ "icon": "assets/map/pin.webp"
283
+ }]
284
+ }
285
+ ```
286
+
287
+ `image` 是相对项目目录的路径或绝对 URL。`spots` 必填。点位 `id` 唯一,每个 `sceneId` 必须指向项目中的场景。`x`、`y` 是归一化图像坐标,范围 [0, 1],原点在左上角。`heading` 是查看器 yaw 为 0 时、从图像向上方向起算的顺时针角度;扇形实际旋转为 `heading + yaw`。多个点位可以指向同一场景,扇形画在第一个 `sceneId` 与当前场景相同的点位上。`icon` 可选;省略时使用内置标记。
288
+
289
+ `interaction` 为 `"fixed"`(默认)或 `"zoom"`。固定模式把图像适配进面板,忽略滚轮、拖拽平移和缩放按钮。缩放模式允许滚轮缩放,并在比例大于适配尺寸后拖拽平移。地图上的滚轮不会带动页面滚动。`maxScale` 是相对适配尺寸的最大倍数,至少为 1,默认 8。`dragMotion` 控制放大后的平移惯性与边界回弹(默认均开启)。设为 `false` 可同时关闭,或传 `{ inertia?: boolean; bounce?: boolean }` 分别开关。点位与朝向编辑仍是直接拖动。`controls.zoom` 仅在 `"zoom"` 时显示缩放按钮。`controls.collapse` 在停靠面板上显示展开和收起。`iconSize` 是标记边长,单位为 CSS 像素,默认 28。图像缩放时,图标和扇形保持屏幕尺寸。
290
+
291
+ `sector` 可选。`angle` 是开口角度,大于 0 且小于 360。`radius` 单位为 CSS 像素,不随图像缩放。`followFov` 用查看器视场角和地图面板宽高比换算水平视场,代替 `angle`。没有 `gradient` 时,填充为纯色 `color`(默认 `#ffffff`),不透明度为 `opacity`(默认 0.55)。`gradient.type` 为 `"radial"` 或 `"linear"`,至少两个色标。每个色标包含 [0, 1] 的 `offset`、`color`,以及可选 `opacity`。色标不透明度只描述渐隐形状。`sector.opacity` 在纯色和渐变两种模式下都缩放整块填充。`strokeColor`、`strokeWidth`、`strokeOpacity` 在宽度大于 0 时绘制描边,描边宽度保持 CSS 像素。
292
+
293
+ `presentation.mode` 为 `"docked"`(默认)或 `"dialog"`。两者都接受 `corner`(`top-left`、`top-right`、`bottom-left`、`bottom-right`,默认 `bottom-left`)、`offsetX` 与 `offsetY`(默认 16),以及 `width` 和 `height`。停靠面板默认 220×160。对话框模式停靠一个打开按钮(未写宽高时为 48×48),并在查看器内部打开同一张地图。选中点位后关闭对话框。地图界面锚定在整个查看器 overlay 上,父页面滚动不会移动或改变它的尺寸。
294
+
295
+ 文档保存默认值(`image`、`spots`、`sector`、`presentation`、`interaction`、`maxScale`、`dragMotion`、`controls`、`iconSize`)。挂载 `PanoMapOverlay` 时可用组件 props 覆盖本次展示的尺寸、位置和缩放,不必克隆整份 `map`。优先级为:内置默认值 → 地图文档 → `presentationOverride`(形状与 `map.presentation` 相同)→ 单独 props。点位几何与扇形样式仍写在文档里。`selectedSpotId` / `onSelectedSpotIdChange` 会向下传给内部 `PanoMap`。停靠模式的收起状态由 overlay 内部管理;对话框模式与 `editorSurface` 会强制关掉收起控件,不能从外面控制 `collapsed`。
296
+
297
+ #### 挂载 `PanoMapOverlay`
298
+
299
+ 在 `PanoOverlay` 中挂载,并把交给 `ProjectScenes` 的同一个 `activeSceneId` 传进去。内置 `pano-map-*` 类名来自 `@ericchen1990/pano-view/styles.css`。overlay 从查看器读取 yaw 和视场角,不会自己切换场景:在 `onSelectScene` 里设置当前场景。相机状态来自查看器之外时传入 `view`;查看器外又不传 `view` 时,图面停在 yaw `0`、pitch `0`、fov `75`。`PanoMap` 只负责图面,由宿主自己安排外框。
300
+
301
+ `PanoMap` 还可受控选中与收起:`selectedSpotId` / `onSelectedSpotIdChange`(省略时内部选中,默认 `null`);`collapsed` / `onCollapsedChange`(非受控初始为展开);`renderChrome` 在图面之上渲染宿主控件。`showZoomControls` / `showCollapseControl` 与 overlay 相同,也可直接传给 `PanoMap`。`className` / `style` 挂在表面根节点上。
302
+
303
+ ```tsx
304
+ import { useState } from "react";
305
+ import {
306
+ PanoOverlay, PanoViewer, ProjectScenes, PanoMapOverlay, type LoadedPanoProject,
307
+ } from "@ericchen1990/pano-view";
308
+ import "@ericchen1990/pano-view/styles.css";
309
+
310
+ export function Tour({ project }: { project: LoadedPanoProject }) {
311
+ const [activeSceneId, setActiveSceneId] = useState(project.initialSceneId);
312
+ const map = project.map;
313
+ return <PanoViewer style={{ height: 600 }}>
314
+ <ProjectScenes
315
+ project={project}
316
+ activeSceneId={activeSceneId}
317
+ onActiveSceneIdChange={setActiveSceneId}
318
+ />
319
+ {map ? (
320
+ <PanoOverlay>
321
+ <PanoMapOverlay
322
+ map={map}
323
+ activeSceneId={activeSceneId}
324
+ onSelectScene={setActiveSceneId}
325
+ />
326
+ </PanoOverlay>
327
+ ) : null}
328
+ </PanoViewer>;
329
+ }
330
+ ```
331
+
332
+ #### 尺寸、角落与边距
333
+
334
+ - `presentation` — `"docked"` | `"dialog"`(默认取自文档,否则为 `"docked"`)。
335
+ - `corner` — `"top-left"` | `"top-right"` | `"bottom-left"` | `"bottom-right"`(默认 `"bottom-left"`)。
336
+ - `width` / `height` — 停靠面板尺寸,或对话框打开按钮尺寸,单位为 CSS 像素。停靠默认 220×160;打开按钮默认 48×48。
337
+ - `offsetX` / `offsetY` — 相对所选角落的边距,单位为 CSS 像素(默认 16)。
338
+
339
+ ```tsx
340
+ <PanoMapOverlay
341
+ map={map}
342
+ activeSceneId={activeSceneId}
343
+ onSelectScene={setActiveSceneId}
344
+ presentation="docked"
345
+ corner="bottom-right"
346
+ width={280}
347
+ height={200}
348
+ offsetX={20}
349
+ offsetY={20}
350
+ />
351
+ ```
352
+
353
+ #### 对话框模式
354
+
355
+ 对话框模式停靠一个打开按钮,并在查看器内部打开地图。打开按钮尺寸使用 `width` / `height`(以及 `corner` 与边距)。对话框本体使用另一组尺寸:
356
+
357
+ - `dialogWidth` / `dialogHeight` — 期望的对话框宽高,单位为 CSS 像素(默认 720×520)。两者都会按查看器可用区域夹紧,四周各留 16 像素边距。
358
+ - `openLabel` / `closeLabel` / `title` — 打开按钮无障碍名称、关闭按钮文案与对话框标题(默认分别为 `"Map"`、`"Close"`、`"Map"`)。
359
+
360
+ 选中点位后关闭对话框。
361
+
362
+ ```tsx
363
+ <PanoMapOverlay
364
+ map={map}
365
+ activeSceneId={activeSceneId}
366
+ onSelectScene={setActiveSceneId}
367
+ presentation="dialog"
368
+ corner="bottom-left"
369
+ width={52}
370
+ height={52}
371
+ dialogWidth={640}
372
+ dialogHeight={480}
373
+ openLabel="Floor plan"
374
+ title="Floor plan"
375
+ closeLabel="Close"
376
+ />
377
+ ```
378
+
379
+ #### 缩放与控件
380
+
381
+ 下列 props 仅覆盖本次挂载,不写回地图文档:
382
+
383
+ - `interaction` — `"fixed"` | `"zoom"`。
384
+ - `maxScale` — 相对适配尺寸的最大倍数;小于 1 时按 1 处理。
385
+ - `dragMotion` — `interaction` 为 `"zoom"` 时的平移惯性 / 边界回弹。省略则两者都开;`false` 同时关闭。
386
+ - `initialScale` — 打开时的缩放倍数,相对适配尺寸,限制在 `[1, maxScale]`。这是视图状态,不会写回文档。图像测量完成后居中应用。仅在 `initialScale` 或 `interaction` 变化时重置;之后滚轮与拖拽保持用户操作。`maxScale` 变小时只夹紧当前缩放。
387
+ - `showZoomControls` / `showCollapseControl` — 强制显示或隐藏缩放、收起控件。
388
+ - `iconSize` — 标记边长,单位为 CSS 像素。
389
+
390
+ ```tsx
391
+ <PanoMapOverlay
392
+ map={map}
393
+ activeSceneId={activeSceneId}
394
+ onSelectScene={setActiveSceneId}
395
+ interaction="zoom"
396
+ maxScale={4}
397
+ initialScale={1.5}
398
+ showZoomControls
399
+ showCollapseControl
400
+ iconSize={32}
401
+ />
402
+ ```
403
+
404
+ #### 编辑模式
405
+
406
+ `mode="edit"` 用于编辑:设置 `placementSceneId` 后点击地图会新增点位,其 `heading` 取当前 yaw 的相反数,因此在该 yaw 下扇形指向图像上方;拖动点位改变位置,拖动扇形设置 `heading`。`onEditChange` 返回 `{ spots?, selectedSpotId? }`。访客播放保持默认的 `mode="view"`。`editorSurface` 会放大编辑面板;传入 `width` / `height` 可指定尺寸,否则按查看器比例计算。
407
+
408
+ 运行 `pnpm dev` 并打开 `/map` 可查看示例。同一份文档的编辑方式见[编辑器平面图](../editor/README.zh-CN.md#平面图)。
409
+
410
+ 继续使用 schemaVersion 1,旧项目仍可读取。包含新动作或平面图的项目需使用支持它们的查看器。`parsePanoProject(input, { draft: true })` 仅用于编辑草稿,允许空场景列表、未完成的弹窗标题/媒体,以及空的平面图路径;加载、保存和导出仍严格校验。宿主可将同一份文档存入数据库或映射到表,查看器不直接连接数据库。
243
411
 
244
412
  单图和已有配置可用纯数据入口;Next.js 的查看器组件需位于客户端边界,服务端加载数据则使用 `/data`:
245
413
 
@@ -270,14 +438,14 @@ const project = await loadPanoProject({
270
438
 
271
439
  | 引用 | 默认基准 |
272
440
  | --- | --- |
273
- | 项目中的 `viewerUrl`、热点资源及 `source.baseUrl` | 项目根目录 |
441
+ | 项目中的 `viewerUrl`、热点资源、平面图与点位图标,以及 `source.baseUrl` | 项目根目录 |
274
442
  | 外部 `viewer.json` 的瓦片与预览 | 该 JSON 的目录 |
275
443
  | 内嵌配置的瓦片与预览 | 项目根目录 |
276
444
  | 设置 `source.baseUrl` 的场景资源 | 指定目录 |
277
445
 
278
446
  URL 入口以重定向后最终地址所在目录为默认根目录;对象入口必须提供 `baseUrl`,可用它覆盖项目根目录。目录末尾斜杠自动统一,域名和云前缀无需写回转换 JSON。浏览器接受相对入口地址,Node.js 需要绝对 HTTP(S) 地址。完整资源 URL 不重新拼接,`/` 开头的地址相对资源基准的 origin 解析。
279
447
 
280
- 上传时若改名或打散目录,使用同步 `resolveAssetUrl(path, context)`。context 提供 `defaultUrl`、`kind`(`tile`、`preview` 或 `hotspot`)、`sceneId` 和可选 `hotspotId`。返回最终的完整 URL 或站点根路径;不会再次拼接。`uploadedUrls` 由业务提供:
448
+ 上传时若改名或打散目录,使用同步 `resolveAssetUrl(path, context)`。context 提供 `defaultUrl`、`kind`(`tile`、`preview`、`hotspot` 或 `map`)、`sceneId` 和可选 `hotspotId`。返回最终的完整 URL 或站点根路径;不会再次拼接。`uploadedUrls` 由业务提供:
281
449
 
282
450
  ```ts
283
451
  const project = await loadPanoProject("/tour/project.json", {
@@ -289,13 +457,13 @@ const project = await loadPanoProject("/tour/project.json", {
289
457
  });
290
458
  ```
291
459
 
292
- 热点映射涵盖图片、SVG 图片、音频数组、视频 poster、播放图标和 tooltip 图片;文本、CSS 和 SVG path 不作为地址处理。临时签名应由业务预先取得并通过映射返回,不要在每个瓦片请求中异步签名。
460
+ 热点映射涵盖图片、SVG 图片、音频数组、视频 poster、播放图标和 tooltip 图片;文本、CSS 和 SVG path 不作为地址处理。平面图和点位图标走同一个回调,`kind` 为 `"map"`。平面图的 `sceneId` 为空字符串,且没有 `hotspotId`;点位图标的 `sceneId` 和 `hotspotId` 都是点位 id。同一路径会被多种 `kind` 使用时,上传键要带上 `kind`。临时签名应由业务预先取得并通过映射返回,不要在每个瓦片请求中异步签名。
293
461
 
294
462
  `requestInit` 只接受 `headers`、`credentials`、`signal`,仅用于 JSON 请求。它们不会传到纹理请求,JSON 地址的查询参数也不会自动继承。`parsePanoProject(unknown)` 可单独校验并复制配置。`sceneFromViewerConfig` 接受未包装的精简配置;`loadSceneFromJson` 接受带 `schemaVersion: 1` 的 `viewer.json`。
295
463
 
296
464
  `PanoDataError` 提供稳定 `code`:`INVALID_OPTIONS`、`FETCH_FAILED`、`HTTP_ERROR`、`INVALID_JSON`、`INVALID_CONFIG`、`UNSUPPORTED_SCHEMA_VERSION`、`UNSUPPORTED_PROJECTION`、`INVALID_PROJECT`、`INVALID_REFERENCE`、`ABORTED`;HTTP 错误带 `status`,项目错误尽可能带 `sceneId` 和 `field`。加载成功仅代表配置就绪,纹理就绪仍由 `onSceneReady` 等回调表示。
297
465
 
298
- 完整静态示例见 playground 的 `/project` 页面和 `public/fixtures/projects/tour/project.json`。
466
+ 完整静态示例见 playground 的 `/project` 页面和 `public/fixtures/projects/tour/project.json`。`/map` 在该漫游上展示平面图 overlay。
299
467
 
300
468
  ## 兼容 krpano 的瓦片输出
301
469
 
@@ -357,7 +525,7 @@ bun add @ericchen1990/pano-view
357
525
 
358
526
  在宿主应用中导入一次随包样式表。WebGL 全景(`PanoViewer`、`Sphere`、`Tile`、3D 热点)无需该文件即可渲染。内置 **HTML** 叠加层依赖该文件中带 `pano-` 前缀的类名完成布局。
359
527
 
360
- 这些叠加层包括视频控件、字幕、默认右键菜单、热点 tooltip、供辅助技术使用的界面元素,以及 WebVR 会话 UI。
528
+ 这些叠加层包括视频控件、字幕、默认右键菜单、热点 tooltip、供辅助技术使用的界面元素、WebVR 会话 UI,以及平面图。
361
529
 
362
530
  ```ts
363
531
  import "@ericchen1990/pano-view/styles.css";
@@ -374,7 +542,7 @@ import "@ericchen1990/pano-view/styles.css";
374
542
  ## 示例
375
543
 
376
544
  为 PanoViewer 提供明确尺寸,并在其内部渲染一个全景源。使用内置 HTML 叠加层
377
- (例如视频控件、字幕、tooltip、右键菜单或 WebVR UI)时,需导入一次样式表。
545
+ (例如视频控件、字幕、tooltip、右键菜单、WebVR UI 或平面图)时,需导入一次样式表。
378
546
  下面是每种源的基本用法,完整 props 见对应章节。
379
547
 
380
548
  ### 等距圆柱图像
@@ -488,9 +656,16 @@ export function Tour() {
488
656
  ### 事件与界面元素
489
657
 
490
658
  - [`PanoEvents`](#panoevents) — 查看器级生命周期与交互回调
659
+ - [`PanoOverlay`](#dom-overlay-与弹窗) — 查看器平面上的 DOM 包裹,全屏时仍可见
491
660
  - [`PanoContextMenu`](#panocontextmenu) — 右键重置 / 全屏菜单
661
+ - [`PanoSceneMenu`](#panoscenemenu) — 供 `PanoOverlay` 使用的可滚动缩略图场景菜单
492
662
  - [`PanoVideoControls`](#panovideo) — `PanoVideo` 的播放界面(默认挂载)
493
663
 
664
+ ### 平面图
665
+
666
+ - [`PanoMap`](#平面图地图) — 平面图、场景点位,以及随水平角转动的扇形
667
+ - [`PanoMapOverlay`](#平面图地图) — 供 `PanoOverlay` 使用的停靠面板或对话框按钮
668
+
494
669
  ### 热点
495
670
 
496
671
  - [`ImageHotspot`](#imagehotspot) — 球面位置上的图像纹理
@@ -514,9 +689,25 @@ export function Tour() {
514
689
 
515
690
  ## DOM Overlay 与弹窗
516
691
 
517
- 普通 DOM 界面使用 `PanoViewer.renderOverlay`,位于 Three.js 渲染树之外。`usePanoOverlay()` 提供稳定的 Portal 容器、viewer/可见区域尺寸和全屏状态。可选 `/ui` 入口提供 `PanoDialog` 与组成组件,也可以接入应用已有的 Dialog。
692
+ 普通 DOM 界面优先使用 `PanoOverlay`,位于 Three.js 渲染树之外,挂在全屏根节点内的查看器平面上,因此全屏时仍可见。把它作为 `PanoViewer` 的直接子节点(或包在其下的 Fragment 里),与场景内容并列。`renderOverlay` 仍可用,适合回调式挂载:
518
693
 
519
- 独立的 **[弹窗使用与自定义样式指南](./docs/dialogs.zh-CN.md)** 包含完整示例、CSS 变量默认值与放置位置、布局组合、第三方接入、二级 Portal、生命周期、API 表和排错说明。指南随 npm 包分发。在本仓库运行 `pnpm dev`,打开 `/overlays` 可体验示例。
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` 可体验示例。
520
711
 
521
712
  ### 默认弹窗
522
713
 
@@ -601,12 +792,27 @@ await viewer.zoomTo(48, { duration: 400, easing: "linear" });
601
792
 
602
793
  **场景**(需 `<Scenes />`)— `setScene(id)`、`nextScene`、`previousScene`、`getActiveSceneId`、`getSceneIds`、`isSceneTransitioning`。`Scenes` 支持 `defaultActiveSceneId`(非受控)或 `activeSceneId`(受控)。受控模式下命令式调用会触发 `onActiveSceneIdChange`。
603
794
 
604
- **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」。
605
796
 
606
797
  **视频**(需 `<PanoVideo />`)— `getVideo` 返回 `PanoVideoController`,`subscribeVideo` 订阅视频控制器与播放状态变更,另有快捷方法 `playVideo`、`pauseVideo`、`toggleVideo`、`seekVideo`、`setVideoVolume`、`setVideoMuted`、`toggleVideoMuted`。
607
798
 
608
799
  **背景音乐**(需 `<BackgroundAudio />`)— `getBackgroundAudio` 返回 `BackgroundAudioController`,`subscribeBackgroundAudio` 订阅背景音频控制器与播放状态变更,另有快捷方法 `playBackgroundAudio`、`pauseBackgroundAudio`、`toggleBackgroundAudio`、`setBackgroundAudioVolume`、`setBackgroundAudioMuted`、`toggleBackgroundAudioMuted`。`BackgroundAudio` 支持 `playing`(受控)或 `defaultPlaying`(非受控)。
609
800
 
801
+ ### 属性
802
+
803
+ - `initialView` — 省略时 yaw `0`、pitch `0`、fov `75`。只填部分字段时其余用上述默认值。
804
+ - `minFov` / `maxFov` — 默认 `30` / `100`。未单独配置 `viewLimits.fov` 时作为 FOV 上下限。
805
+ - `viewLimits` — 相机中心的 yaw / pitch / fov 范围;场景级限制会覆盖对应轴。详见[相机中心范围](#相机中心范围)。
806
+ - `dpr` — 画布像素比,默认 `[1, 2]`(按设备像素比夹在 1–2)。也可传单个数字。
807
+ - `controls` — `false` 关掉全部用户输入。对象可配置共享项与 `mouse` / `touch` / `keyboard` 通道;通道细节见[鼠标、触控与键盘控件](#鼠标触控与键盘控件)。共享项中:`enabled` 默认 `true`,`inertia` 默认 `true`,`invert` 默认 `false`,`bouncingLimits` 默认 `false`,`frictionStop` 默认 `0.01`,`rotateSpeed` 默认 `0.35`,`zoomSpeed` 默认 `0.08`,`rotateDamping` / `zoomDamping` 默认 `14` / `16`。`fovSpeed` 省略时不额外限速;滚轮按单次位移 `deltaY * zoomSpeed` 夹到 `±fovSpeed`,键盘按键按「度/秒 × 帧时间」再与 `fovSpeed` 比较。
808
+ - `cursors` — `true` / 省略使用 grab / grabbing / pointer / move;`false` 不改画布光标;对象可覆盖四个状态。
809
+ - `contextMenu` — `true` / 省略挂载「重置视图 + 全屏」;`false` 不挂默认菜单;对象可改外观或 `items` / `prepend` / `append`。详见 [PanoContextMenu](#panocontextmenu)。
810
+ - `mediaActivation` — 有播放意图的子组件时默认开启;`false` 关闭自动激活。
811
+ - `renderOverlay` — 可选的回调式 DOM UI,在画布外、全屏内渲染;回调收到 `portalContainer`、`size`、`visibleRect`、`isFullscreen`。优先用子组件 `<PanoOverlay>`。
812
+ - `onViewChange` — 参数为 `{ yaw, pitch, fov }`(度)。
813
+ - `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` — 参数为全景指针事件:`position`(yaw / pitch)、`nativeEvent`、`button`、`buttons`、`clientX`、`clientY`,以及可选的 `pointerId`。
814
+ - 其余标准 `div` 属性(如 `className`、`style`、`id`)可原样传入;类型已去掉冲突的 `onChange` 与 `contextMenu`。
815
+
610
816
  ### 移动端媒体激活
611
817
 
612
818
  默认情况下,只要子组件带有初始播放意图(`PanoVideo autoPlay`、播放中的 `BackgroundAudio`、`VideoHotspot` 或 `AudioHotspot`),查看器会在浏览器需要用户手势时于首次交互静默解锁有声播放;若自动播放策略已允许,则立即激活。静音视频仍会内联预览;激活在同一用户手势内恢复 Web Audio,再把播放选择交给宿主,避免视频、BGM 与定点声同时出声。传 `mediaActivation={false}` 可保留 2.x 的无自动激活行为。
@@ -700,26 +906,30 @@ function SceneExample() {
700
906
 
701
907
  ### WebVR
702
908
 
703
- `enterVR` 与 `requestVRPermission` 须在用户手势中调用。
909
+ `enterVR` 与 `requestVRPermission` 须在用户手势中调用。通过 `getVRState` /
910
+ `subscribeVR` 由宿主 UI 驱动进入;会话 chrome 只显示退出 VR 与 MobileVR 设置
911
+ (空闲数秒后淡出)。
704
912
 
705
913
  ```tsx
914
+ import { useSyncExternalStore } from "react";
706
915
  import { PanoViewer, Sphere, WebVR, usePanoViewer } from "@ericchen1990/pano-view";
707
916
 
708
917
  function VRExample() {
709
918
  const viewer = usePanoViewer();
919
+ const vr = useSyncExternalStore(
920
+ viewer.subscribeVR,
921
+ viewer.getVRState,
922
+ viewer.getVRState,
923
+ );
710
924
 
711
925
  return (
712
926
  <>
713
927
  <button
714
- onClick={async () => {
715
- await viewer.requestVRPermission();
716
- await viewer.enterVR();
717
- }}
928
+ disabled={!vr.available}
929
+ onClick={() => void (vr.enabled ? viewer.exitVR() : viewer.enterVR())}
718
930
  >
719
- 进入 VR
931
+ {vr.enabled ? "退出 VR" : "进入 VR"}
720
932
  </button>
721
- <button onClick={() => void viewer.exitVR()}>退出 VR</button>
722
- <button onClick={() => console.log(viewer.getVRMode())}>当前模式?</button>
723
933
  <PanoViewer ref={viewer.ref} style={{ height: 560 }}>
724
934
  <WebVR />
725
935
  <Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
@@ -884,7 +1094,7 @@ function TourPlayer() {
884
1094
 
885
1095
  高分辨率全景优先使用立方体瓦片 [`Tile`](#tile),通过 [@ericchen1990/pano-converter](../converter/README.zh-CN.md) 生成资源。查看器会按视野加载所需层级的瓦片,避免使用单张超大纹理。已有等距圆柱瓦片可使用 [`SphereTile`](#球面多分辨率瓦片)。
886
1096
 
887
- `previewUrl` 为必填项,在 `src` 加载期间显示低分辨率 2:1 图像。应提供尺寸较小的预览图,以减少首次显示的下载、解码和纹理内存开销。相对路径相对于 `src` 所在目录解析;根绝对路径与 `http(s)` URL 原样使用。对于 krpano 球面场景,将 [`<preview url>`](https://krpano.com/docu/xml/#preview) 复制到 `previewUrl`。该预览必须是等距圆柱,而非立方体条带。
1097
+ `previewUrl` 为必填项,在 `src` 加载期间显示低分辨率 2:1 图像。应提供尺寸较小的预览图,以减少首次显示的下载、解码和纹理内存开销。相对路径相对于 `src` 所在目录解析;根绝对路径与 `http(s)`、`blob:`、`data:` URL 原样使用。对于 krpano 球面场景,将 [`<preview url>`](https://krpano.com/docu/xml/#preview) 复制到 `previewUrl`。该预览必须是等距圆柱,而非立方体条带。
888
1098
 
889
1099
  ```tsx
890
1100
  "use client";
@@ -903,6 +1113,17 @@ export function SphereExample() {
903
1113
  }
904
1114
  ```
905
1115
 
1116
+ ### 属性
1117
+
1118
+ - `src` — 必填,完整 2:1 等距圆柱图像。
1119
+ - `previewUrl` — 必填,加载期间显示的低分辨率 2:1 预览。
1120
+ - `yawOffset` — 水平旋转,单位为度,默认 `0`。源图前向与查看器 yaw `0` 不一致时设置。
1121
+ - `visible` — 默认 `true`。`false` 时保持挂载并预加载,但不绘制到球面。
1122
+ - `onReady` — 首个可绘制纹理(预览或 `src`)就绪时触发一次。
1123
+ - `onLoad` — `src` 纹理加载完成,参数为 Three.js `Texture`。
1124
+ - `onError` — `src` 加载失败。
1125
+ - `onPreviewError` — 预览加载失败;`src` 仍会继续加载。
1126
+
906
1127
  ## PanoVideo
907
1128
 
908
1129
  `PanoVideo` 将 2:1 等距圆柱视频映射到与 `Sphere` 相同的内表面球体上。可传单个 `src`,或传含多档质量的 `variants`,以便浏览器选择 mp4 / webm,且控制栏可切换分辨率。切换质量时保留当前时间与播放/暂停状态。
@@ -955,6 +1176,30 @@ export function VideoExample() {
955
1176
 
956
1177
  `muted` 默认为 `true`,以提高自动播放的兼容性;实际播放仍受浏览器策略约束。`playsInline` 也默认开启,并写入 `playsinline`、`webkit-playsinline` 以及 X5 的 `h5-page` 属性,向 iPhone Safari、微信、QQ 和 UC 请求页内播放;实际行为取决于浏览器及其版本。画中画和远程播放保持关闭,以便该元素持续作为球面纹理。元素会以 1×1 像素挂在查看器内。非静音播放可能需要用户手势;`play()` 被拒绝时 `onPlaybackStateChange` 会收到 `"blocked"`。非同源的远程视频与 VTT 文件应设置 `crossOrigin`(通常为 `"anonymous"`)。移动端请提供 H.264 + AAC 的 MP4 备选源、poster、CORS 与 Range 支持。
957
1178
 
1179
+ ### 属性
1180
+
1181
+ - `src` — 单文件快捷方式;与 `variants` 二选一。
1182
+ - `poster` — 封面图。多档时也可写在各 `variants[].poster`。
1183
+ - `variants` / `defaultVariantId` — 多分辨率列表;找不到 `defaultVariantId` 时回退到第一项。切换档位保留当前时间与播放/暂停状态。
1184
+ - `tracks` — WebVTT 字幕轨。每项可含 `src`、`srcLang`、`label`、`default`、`id`(省略时用 `srcLang`)、`kind`(`"subtitles"` 或 `"captions"`,默认 `"subtitles"`)。
1185
+ - `captions` — `true` / 省略显示默认字幕层;`false` 隐藏叠加层与语言菜单;对象与默认外观合并。默认外观:`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`。
1186
+ - `controls` — `true` / 省略挂载默认播放栏;`false` 隐藏;对象重设默认实例外观。外观字段:`background`(默认 `rgba(16, 16, 16, 0.88)`)、`color`(`#f4f4f4`)、`accent`(`#75cbd3`)、`borderRadius`(`8`)、`fontSize`(`12`)、`padding`(`"8px 10px"`)。也可将 `PanoVideoControls` 作为 `PanoViewer` 子组件替换默认条(同样接收 `appearance`)。
1187
+ - `yawOffset` — 水平偏移,单位为度,默认 `0`。
1188
+ - `loop` — 默认 `false`。
1189
+ - `muted` — 默认 `true`。
1190
+ - `volume` — 默认 `1`,范围 0–1。
1191
+ - `autoPlay` — 默认 `false`。为 `true` 时会作为媒体激活的触发条件。
1192
+ - `playsInline` — 默认 `true`。
1193
+ - `preload` — `"none"` | `"metadata"` | `"auto"`,默认 `"metadata"`。
1194
+ - `crossOrigin` — `""` | `"anonymous"` | `"use-credentials"`;省略则不设。
1195
+ - `playbackRates` — 倍速菜单列表,默认 `[0.5, 1, 1.25, 1.5, 2]`。
1196
+ - `visible` — 默认 `true`;`false` 时预加载但不绘制。
1197
+ - `onLoad` — 视频可播放时。
1198
+ - `onError` — `{ source: "poster" | "video" | "track", error }`。
1199
+ - `onPlaybackStateChange` — `"playing" | "paused" | "ended" | "blocked"`。
1200
+ - `onEnded` — 播放到结尾(未循环时)。
1201
+ - `onVariantChange` / `onTrackChange` — 当前档位 id,或当前字幕轨 id(无轨时为 `null`)。
1202
+
958
1203
  ## Tile
959
1204
 
960
1205
  `Tile` 渲染六个朝内的立方体面,并仅加载当前视野附近的瓦片。省略 `urlTemplate` 时,相对于 `baseUrl` 的路径使用本包默认:
@@ -1050,6 +1295,24 @@ MAKE PANO (MULTIRES) 文件夹布局常为 `tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg`,
1050
1295
 
1051
1296
  对于 krpano 立方体瓦片模板,`%s` 为立方体面,`%l` 为多分辨率层级。水平瓦片索引支持等价的 `%h`、`%x`、`%u`、`%c` 占位符;垂直索引支持 `%v`、`%y`、`%w`、`%r`。在索引占位符前加零可填充:例如 `%0h` 为两位水平索引,`%00v` 为三位垂直索引。立体 `%t` 与帧 `%f` 占位符不适用于表示单一非立体立方体全景的 `Tile`。
1052
1297
 
1298
+ ### 属性
1299
+
1300
+ - `baseUrl` / `multires` / `previewUrl` — 必填。`multires` 可为字符串或 `{ tileSize, levels }`。
1301
+ - `previewFaceOrder` — 默认 `["l", "f", "r", "b", "u", "d"]`,须恰好六个面。
1302
+ - `urlTemplate` — 省略时用本包默认路径;`resolveTileUrl` 优先于模板。
1303
+ - `maxTextureMemoryMb` — 独立 `Tile` 默认 `128`。
1304
+ - `maxConcurrentLoads` — 独立 `Tile` 默认 `8`(不要与 `SphereTile` / `Scenes` 的 `4` 混淆)。
1305
+ - `retryCount` — 首次失败后的重试次数,默认 `1`。
1306
+ - `loadMode` — `"full"`(默认)、`"preview"` 或 `"base"`。
1307
+ - `visible` — 默认 `true`;`false` 时仅预加载。
1308
+ - `onReady` — 预览图可绘制时触发(预览失败时仍可能由瓦片就绪触发,见实现)。
1309
+ - `onPreviewError` — 预览加载失败。
1310
+ - `onLoadProgress` — `{ requested, loaded, failed, active, queued }`。
1311
+ - `onTileError` — `{ address, url, error }`。
1312
+ - `onLevelChange` — 当前立方体层级,**从 1 起计**(`SphereTile` 的逻辑层级从 0 起,不要混用)。
1313
+
1314
+ 放进 `Scenes` 的 `type: "tile"` 场景后,共享纹理缓存会覆盖场景上的 `maxTextureMemoryMb`、`maxConcurrentLoads`、`retryCount`(共享提供者将 retry 写为 `1`)。可见性、`loadMode` 与上述回调由 `Scenes` 接管,不要写在场景对象上。
1315
+
1053
1316
  ## Scenes
1054
1317
 
1055
1318
  `Scenes` 支持单张等距圆柱图像、立方体瓦片与等距圆柱瓦片场景之间的切换,并支持受控与非受控模式。过渡使用 GPU 帧缓冲快照与目标场景进行混合。目标场景在隐藏状态下加载预览;瓦片场景同时加载视野附近的最低分辨率层级。目标场景首次可绘制后,当前帧缓冲成为临时 GPU 纹理,并释放旧场景的源纹理。目标随即进入 `full`:转场期间已经清晰的瓦片立刻盖住预览,尚未就绪的区域继续显示父层瓦片或预览。旧场景释放后,目标场景才开始加载细节瓦片;瓦片选择范围包含当前视野及其周边的少量预留区域。
@@ -1092,6 +1355,195 @@ const scenes: Scene[] = [
1092
1355
 
1093
1356
  细节加载等待或转场进行时,全景拖拽/缩放输入被锁定,`renderHotspots` 被隐藏。新的 `activeSceneId` 会取代仍在准备中的目标。`maxTextureMemoryMb` 与 `maxConcurrentTileLoads` 作用于整个 `Scenes` 查看器,而非每个瓦片场景单独计算。
1094
1357
 
1358
+ ### 属性
1359
+
1360
+ - `scenes` — 必填。支持 `type: "sphere" | "tile" | "sphereTile"`。
1361
+ - `activeSceneId` — 受控当前场景。
1362
+ - `defaultActiveSceneId` — 非受控初始场景。两者都省略时使用 `scenes[0].id`。
1363
+ - `onActiveSceneIdChange` — 受控模式下命令式切换会触发。
1364
+ - `transition` — 省略时为 `"crossfade"`、时长 `1` 秒。也可传 `{ preset, duration }`。各预设默认秒数:`none` 0,`crossfade` / `whiteFlash` / 多数 slide 与 open / `pixelate` / `dissolve` / `clockWipe` / `zoomBlur` 为 1,`verticalOpen` 0.7,`glitch` 0.8,`hexDissolve` 1.1,六种 `gridWipe*` / `shatter` / `swirl` / `ripple` 为 1.2,`filmBurn` 1.3,`particles` 1.4,`zoom` / `blackout` 为 2。
1365
+ - `transitionWaitForDetailMs` — 默认 `0`。
1366
+ - `renderHotspots` — 仅在过渡结束后渲染;过渡中隐藏。
1367
+ - `onSceneReady` — 首次可绘制,或快照后新场景接管;不等于细节加载完成。
1368
+ - `onTransitionEnd` — `{ previousSceneId, sceneId, preset }`。
1369
+ - `onTransitionError` — `{ sceneId, error }`。
1370
+ - `maxTextureMemoryMb` — 共享预算,默认 `128`。
1371
+ - `maxConcurrentTileLoads` — 共享并发,默认 `4`。
1372
+ - `snapshotMaxPixels` — 旧画面 GPU 快照的最大物理像素面积,默认 `3686400`(约 2560×1440)。
1373
+
1374
+ 场景对象上的 `viewLimits` 见 FAQ;`yawOffset` 语义与对应源组件相同。`TileScene` 上的单场景缓存字段在共享缓存下会被忽略,见 [Tile](#tile)。
1375
+
1376
+ ## PanoSceneMenu
1377
+
1378
+ `PanoSceneMenu` 是可滚动的缩略图场景菜单。通过 `PanoOverlay` 挂载,这样在普通布局和全屏里都会留在 viewer 内。默认布局需导入 `@ericchen1990/pano-view/styles.css`。
1379
+
1380
+ 菜单由宿主驱动:传入 `items`、`activeId`、`onSelect`。它不读取 `Scenes` 状态,也不使用场景的 `previewUrl`。立方体场景的 `previewUrl` 通常是 1×6 预览条,不适合直接当菜单缩略图;请改用独立缩略图(转换器输出可优先用 `previews/200.webp` 这类小图)。
1381
+
1382
+ ### 基础接入
1383
+
1384
+ 与 `Scenes` 共用同一个受控场景 id:
1385
+
1386
+ ```tsx
1387
+ import { useState } from "react";
1388
+ import {
1389
+ PanoOverlay,
1390
+ PanoSceneMenu,
1391
+ PanoViewer,
1392
+ Scenes,
1393
+ type PanoSceneMenuItem,
1394
+ } from "@ericchen1990/pano-view";
1395
+ import "@ericchen1990/pano-view/styles.css";
1396
+
1397
+ const menuItems: PanoSceneMenuItem[] = [
1398
+ {
1399
+ id: "lobby",
1400
+ title: "大厅",
1401
+ thumbnailUrl: "/panoramas/lobby/thumb.webp",
1402
+ },
1403
+ {
1404
+ id: "terrace",
1405
+ title: "露台",
1406
+ thumbnailUrl: "/panoramas/terrace/previews/200.webp",
1407
+ },
1408
+ ];
1409
+
1410
+ export function Tour() {
1411
+ const [activeSceneId, setActiveSceneId] = useState("lobby");
1412
+
1413
+ return (
1414
+ <PanoViewer style={{ height: 560 }}>
1415
+ <Scenes
1416
+ scenes={scenes}
1417
+ activeSceneId={activeSceneId}
1418
+ onActiveSceneIdChange={setActiveSceneId}
1419
+ />
1420
+ <PanoOverlay>
1421
+ <PanoSceneMenu
1422
+ activeId={activeSceneId}
1423
+ items={menuItems}
1424
+ onSelect={setActiveSceneId}
1425
+ />
1426
+ </PanoOverlay>
1427
+ </PanoViewer>
1428
+ );
1429
+ }
1430
+ ```
1431
+
1432
+ 每项需要 `id` 与 `thumbnailUrl`。可选 `title` 显示在缩略图底部标签和控制提示上;省略时用 `id`。当前项带叠在缩略图上的选择边框和 `data-active="true"`。场景名默认叠在缩略图底部的暗色半透明条上,可用 `appearance.labelAlign` 改为居中或右对齐。
1433
+
1434
+ ### 位置、边距与尺寸
1435
+
1436
+ - `placement` — `"top"` | `"bottom"` | `"left"` | `"right"`(默认 `"bottom"`)。上下为横向列表,左右为纵向列表。
1437
+ - `align` — `"start"` | `"center"` | `"end"`(默认 `"center"`)。菜单主轴尺寸小于可用空间时,沿停靠边对齐;列表内容短于视口时同样对齐。
1438
+ - `inset` — 与 viewer 边缘的间隙(默认 `12`)。数字按像素;字符串可带单位(如 `"1rem"`);对象可分别设置 `top` / `right` / `bottom` / `left`。
1439
+ - `width` / `height` — 菜单宽高。数字按像素;字符串会原样写入 CSS,因此可用 `%`、`rem`、`calc()`、`min()`、`max()`、`clamp()` 等。
1440
+ - `dragMotion` — 滚动惯性与边界回弹。省略则两者都开(自定义指针滚动,桌面也可拖)。设为 `false` 使用原生 overflow 滚动。可传 `{ inertia?: boolean; bounce?: boolean }` 分别开关。短按仍会选中;拖过数像素后会抑制 click。
1441
+
1442
+ ```tsx
1443
+ <PanoSceneMenu
1444
+ placement="bottom"
1445
+ align="center"
1446
+ inset={16}
1447
+ width="min(720px, 100%)"
1448
+ items={menuItems}
1449
+ activeId={activeSceneId}
1450
+ onSelect={setActiveSceneId}
1451
+ />
1452
+
1453
+ <PanoSceneMenu
1454
+ placement="right"
1455
+ align="start"
1456
+ inset={{ top: 24, right: 12, bottom: 24, left: 12 }}
1457
+ height="70%"
1458
+ width={160}
1459
+ items={menuItems}
1460
+ activeId={activeSceneId}
1461
+ onSelect={setActiveSceneId}
1462
+ />
1463
+ ```
1464
+
1465
+ 上下布局若省略 `width`,菜单会在左右 `inset` 之间拉伸;左右布局若省略 `height`,会在上下 `inset` 之间拉伸。组件还会按 `inset` 和安全区设置 `max-width` / `max-height`,所以即使用 `100%` 也不会超出 viewer。
1466
+
1467
+ 需要临时覆盖停靠位置时,可再叠一层 `style`。
1468
+
1469
+ ### 展开收起与箭头
1470
+
1471
+ 默认始终展开。设 `collapsible` 后会出现开关,位于缩略图条朝向画面内侧的正中(底部菜单在条上方,顶部在条下方,左右在朝向画面一侧)。收起时列表沿停靠边滑出屏幕,只把展开按钮留在安全区边缘:
1472
+
1473
+ ```tsx
1474
+ <PanoSceneMenu
1475
+ collapsible
1476
+ defaultExpanded
1477
+ items={menuItems}
1478
+ activeId={activeSceneId}
1479
+ onSelect={setActiveSceneId}
1480
+ />
1481
+
1482
+ // 受控
1483
+ <PanoSceneMenu
1484
+ collapsible
1485
+ expanded={menuOpen}
1486
+ onExpandedChange={setMenuOpen}
1487
+ items={menuItems}
1488
+ activeId={activeSceneId}
1489
+ onSelect={setActiveSceneId}
1490
+ />
1491
+ ```
1492
+
1493
+ 设 `showArrows` 会在列表两端显示上一场景 / 下一场景按钮。默认到首尾后停住;设 `loop` 后箭头与方向键会在首尾环绕(最后一项的下一项是第一项)。场景少于 2 个时箭头仍禁用。可用 `previousLabel` / `nextLabel` 自定义箭头无障碍名称,用 `expandLabel` / `collapseLabel` 自定义展开收起文案。默认文案为 `"Previous scene"`、`"Next scene"`、`"Show scenes"`、`"Hide scenes"`。单选组标签默认是 `"Scenes"`,可通过 `label` 修改。`defaultExpanded` 在可折叠时默认为 `true`。
1494
+
1495
+ ```tsx
1496
+ <PanoSceneMenu
1497
+ showArrows
1498
+ loop
1499
+ collapsible
1500
+ placement="bottom"
1501
+ width="min(720px, 100%)"
1502
+ items={menuItems}
1503
+ activeId={activeSceneId}
1504
+ onSelect={setActiveSceneId}
1505
+ previousLabel="上一房间"
1506
+ nextLabel="下一房间"
1507
+ />
1508
+ ```
1509
+
1510
+ ### 外观与样式钩子
1511
+
1512
+ 常用外观可用 `appearance`,不必手写 CSS。当前项边框叠在缩略图上方,可用 `selectionColor` / `selectionWidth` 调节(`selectionWidth: 0` 隐藏);场景名默认左对齐,设 `labelAlign: "center"` 或 `"end"` 可改对齐。
1513
+
1514
+ ```tsx
1515
+ <PanoSceneMenu
1516
+ appearance={{
1517
+ background: "rgba(8, 12, 18, 0.88)",
1518
+ itemWidth: 144,
1519
+ itemHeight: 88,
1520
+ gap: 10,
1521
+ borderRadius: 10,
1522
+ selectionColor: "#7dd3fc",
1523
+ selectionWidth: 2,
1524
+ labelBackground: "rgba(0, 0, 0, 0.6)",
1525
+ labelColor: "#ffffff",
1526
+ labelAlign: "center",
1527
+ fontSize: 12,
1528
+ controlColor: "#f8fafc",
1529
+ controlBackground: "rgba(255, 255, 255, 0.14)",
1530
+ }}
1531
+ items={menuItems}
1532
+ activeId={activeSceneId}
1533
+ onSelect={setActiveSceneId}
1534
+ />
1535
+ ```
1536
+
1537
+ 默认值见导出的 `DEFAULT_PANO_SCENE_MENU_APPEARANCE`(含 `padding: "8px 10px"`、`labelAlign: "start"`)。更细的样式可用 `className` / `style` 覆盖这些稳定类名:
1538
+
1539
+ - `pano-scene-menu` — 根容器
1540
+ - `pano-scene-menu-body` — 箭头与列表所在行/列(承载条背景)
1541
+ - `pano-scene-menu-list` — 可滚动列表
1542
+ - `pano-scene-menu-item` / `pano-scene-menu-thumb` / `pano-scene-menu-label`
1543
+ - `pano-scene-menu-control` / `pano-scene-menu-arrow` / `pano-scene-menu-toggle`
1544
+
1545
+ 根节点还带 `data-placement`、`data-align`、`data-orientation`(`horizontal` | `vertical`)、`data-expanded`、`data-collapsible`;当前项带 `data-active="true"`。
1546
+
1095
1547
  ## PanoFilter
1096
1548
 
1097
1549
  在 `PanoViewer` 内渲染 `PanoFilter` 可对全景源(`Sphere`、`Tile`、`SphereTile`、`PanoVideo`)调色或风格化。内置预设仅作用于全景材质;`colorMatrix` 默认仅作用于全景,设 `colorMatrixScope="all"` 可同时作用于热点纹理。tooltip、视频控件与 iframe 等 DOM 叠加层不受影响。WebXR / MobileVR 使用相同的全景着色器,因此继承相同的滤镜外观。
@@ -1128,7 +1580,7 @@ const scenes: Scene[] = [
1128
1580
 
1129
1581
  ## AutoRotate
1130
1582
 
1131
- 自动旋转默认关闭。在 `PanoViewer` 内渲染 `AutoRotate` 以启用,并将旋转配置与用户输入控件分离。`speed` 为度/秒;负值向左旋转。`acceleration` 为度/秒²,从零平滑加速到 `speed`(默认 `18`,即默认速度约 1 秒达到)。设为 `0` 则立即以固定速度旋转。`startDelay` 为 `enabled` 变为 true 起的毫秒延迟。用户拖拽中,或拖拽惯性仍在衰减时,旋转暂停并在恢复时从零速度重新开始。
1583
+ 自动旋转默认关闭。在 `PanoViewer` 内渲染 `AutoRotate` 以启用,并将旋转配置与用户输入控件分离。`speed` 为度/秒;负值向左旋转。`acceleration` 为度/秒²,从零平滑加速到 `speed`(默认 `18`,即默认速度约 1 秒达到)。设为 `0` 则立即以固定速度旋转。`startDelay` 为 `enabled` 变为 true 起的毫秒延迟,默认 `2`。用户拖拽中,或拖拽惯性仍在衰减时,旋转暂停并在恢复时从零速度重新开始。组件挂载且 `enabled` 省略时按 `true` 处理;不渲染组件则不旋转。
1132
1584
 
1133
1585
  ```tsx
1134
1586
  <PanoViewer style={{ height: 560 }}>
@@ -1187,6 +1639,22 @@ import { BackgroundAudio, PanoViewer, Scenes } from "@ericchen1990/pano-view";
1187
1639
 
1188
1640
  需要将声源定位在全景某一点,并使立体声像随视角变化时,使用 `AudioHotspot`。
1189
1641
 
1642
+ ### 属性
1643
+
1644
+ - `src` / `sources` — `src` 为全局轨,或 `sources` 缺项时的回退。二者均可为字符串,或字符串数组(Howler 多格式回退)。`sources` 的键是 `Scene.id`。
1645
+ - `sceneId` — 使用 `sources` 时必填;省略会抛错。
1646
+ - `playing` / `defaultPlaying` — 受控播放,或非受控初始值(默认 `false`)。
1647
+ - `loop` — 默认 `true`。
1648
+ - `muted` — 默认 `false`。
1649
+ - `volume` — 默认 `1`,范围 0–1。
1650
+ - `pauseWhenHidden` — 默认 `true`;页面隐藏时暂停。
1651
+ - `fadeMs` — 切轨交叉淡化毫秒,默认 `400`;`0` 为硬切。
1652
+ - `crossOrigin` — `""` | `"anonymous"` | `"use-credentials"`;省略则不设。
1653
+ - `onPlaybackStateChange` — `"playing" | "paused" | "ended" | "blocked"`。
1654
+ - `onPlaybackError` — `play()` 等播放失败。
1655
+ - `onEnded` — 播完;非受控时还会把内部播放设为 `false`。
1656
+ - `onError` — 资源加载失败,参数 `{ error }`(与 `onPlaybackError` 不同)。
1657
+
1190
1658
  ## Gyro
1191
1659
 
1192
1660
  `Gyro` 为可选的设备方向控制,默认关闭。在 `PanoViewer` 内渲染并通过 `enabled` 控制。相对模式(默认)将启用时的视图作为起始方向。设 `absolute` 可跟随罗盘朝向,用 `north` 指定指向北方的全景 yaw。
@@ -1246,9 +1714,25 @@ export function GyroExample() {
1246
1714
 
1247
1715
  `GyroHandle` 暴露 `resetSensor(yaw?, pitch?)`、`isAvailable()`、`isEnabled()` 与 `requestPermission()`。陀螺仪活动会暂停 `AutoRotate`,并计为查看器交互以用于空闲追踪。传感器 API 通常需要 HTTPS。跨域 iframe 需宿主页允许,例如 `allow="gyroscope; accelerometer"` 与兼容的 `Permissions-Policy` 头。
1248
1716
 
1717
+ ### 属性
1718
+
1719
+ - `enabled` — 默认 `false`。iOS 须在用户手势里先 `requestPermission()` 再设为 `true`。
1720
+ - `camroll` — 默认 `true`。
1721
+ - `absolute` — 默认 `false`(相对模式);`true` 跟随罗盘。
1722
+ - `north` — 表示正北的全景 yaw,单位为度,默认 `0`。
1723
+ - `friction` — `0`–`0.99`,默认 `0`。
1724
+ - `touchMode` — 默认 `"full"`;取值见上文。
1725
+ - `softstart` — 启用时的混合秒数,默认 `0.5`。
1726
+ - `desktopSupport` — 默认 `false`。
1727
+ - `onAvailable` / `onUnavailable` — 传感器能力探测结果变化时。
1728
+ - `onEnable` / `onDisable` — 会话实际开启 / 关闭时。
1729
+ - `onDenied` — 权限被拒绝时(示例中常用来把 `enabled` 设回 `false`)。
1730
+
1731
+ 这些是 `Gyro` 组件自身的回调。`PanoEvents` 上的 `onGyro*` 订阅同一事件总线,供查看器级监听使用。
1732
+
1249
1733
  ## WebVR
1250
1734
 
1251
- `WebVR` 为最近的 `PanoViewer` 增加沉浸式观看。支持的头戴设备优先使用 `immersive-vr` WebXR 会话。无 WebXR 的手机使用设备方向的立体分屏;桌面可提供鼠标驱动的模拟 VR 预览。内置界面包括进入 VR、退出 VR 与 MobileVR 设置控件。
1735
+ `WebVR` 为最近的 `PanoViewer` 增加沉浸式观看。支持的头戴设备优先使用 `immersive-vr` WebXR 会话。无 WebXR 的手机使用设备方向的立体分屏;桌面可提供鼠标驱动的模拟 VR 预览。进入 VR 由宿主通过查看器句柄触发;内置 chrome 仅在会话中显示退出 VR 与 MobileVR 设置(空闲后淡出)。
1252
1736
 
1253
1737
  ```tsx
1254
1738
  import { useRef } from "react";
@@ -1277,10 +1761,34 @@ export function WebVRExample() {
1277
1761
  }
1278
1762
  ```
1279
1763
 
1280
- 句柄暴露 `enterVR()`、`exitVR()`、`toggleVR()`、`isAvailable()`、`isEnabled()`、`getMode()` 与 `requestPermission()`。模式为 `"webxr"`、`"mobilevr"`、`"fake"`。`profile` 接受 `"none"`、`"cardboard-v1"`、`"cardboard-v2"`、`"gear-vr"`、`"daydream"`,或自定义 `{ fov, ipdMm, k1, k2 }` 镜头配置。MobileVR 设置值会版本化并保存在当前源的 `localStorage` 中。VR 中中心准星显示十字;注视可交互热点时绘制顺时针填充环,经 `cursorDwellMs`(默认 1500)后触发热点点击。设 `cursorDwellMs={0}` 可保留准星但不自动点击。模拟桌面 VR(`fake` 模式)默认请求指针锁定(`mousePointerLock`),隐藏光标并用鼠标环视;若浏览器释放锁定需再次点击视图。回退会话通过 Screen Wake Lock API 或在不支持时的循环隐藏视频保持屏幕常亮(`wakelock`)。
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`)。
1281
1765
 
1282
1766
  WebXR 需要 HTTPS 与用户手势。移动端传感器访问也需要 HTTPS,iOS 可能需显式权限提示。嵌入查看器需要适当的 `xr-spatial-tracking`、`gyroscope`、`accelerometer` 权限。iframe 热点、tooltip、视频控件等 DOM 叠加层在沉浸式头戴会话中不可见;基于 mesh 的热点仍可通过默认 WebXR 控制器射线交互。
1283
1767
 
1768
+ ### 属性
1769
+
1770
+ - `chrome` — 默认 `true` 挂载会话内退出 / 设置界面(不含进入 VR 按钮)。也可传外观对象:`background`(默认 `rgba(16, 24, 32, 0.78)`)、`color`(`#ffffff`)、`accent`(`#7dd3fc`)、`borderRadius`(`6`)、`fontSize`(`14`)、可选 `setupTitle`。会话按钮约 3 秒无操作后淡出,与查看器的指针 / 滚轮 / 键盘交互后再次显示。
1771
+ - `mobileVr` — 默认 `true`。
1772
+ - `desktopSupport` — 默认 `false`(与 `Gyro` 的同名 prop 独立)。
1773
+ - `fakeSupport` — 默认 `true`,允许鼠标驱动的桌面模拟 VR。
1774
+ - `screensize` — `"auto"`(出厂 / 省略时的存储默认)或英寸数;数字会被夹到 **3–20** 并保留一位小数。
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"`)。
1784
+ - `cursor` — 默认 `true`;`false` 关闭中心准星。
1785
+ - `cursorDwellMs` — 默认 `1500`;`0` 不自动点击。
1786
+ - `wakelock` / `mousePointerLock` — 默认均为 `true`。
1787
+ - `onAvailable` / `onUnavailable` — VR 能力变化。
1788
+ - `onEnterVR` / `onExitVR` — 参数为模式 `"webxr" | "mobilevr" | "fake"`。
1789
+ - `onDenied` — 权限或会话被拒绝,参数为可选 `error`。
1790
+ - `onUnknownDevice` — `"auto"` 屏幕尺寸无法从 DPDB 解析时(URL 关闭、无匹配或请求失败)。
1791
+
1284
1792
  ## 鼠标、触控与键盘控件
1285
1793
 
1286
1794
  `PanoViewer` 默认启用 `MouseControls`、`TouchControls` 与 `KeyboardControls`。通过 `controls` 配置共享选项;仅当需要覆盖某通道属性时渲染对应控件组件。子实例会替换默认实例 — 无需先将通道设为 `false`。将 `controls.mouse` / `touch` / `keyboard` 设为 `false` 可关闭该通道。
@@ -1304,15 +1812,36 @@ WebXR 需要 HTTPS 与用户手势。移动端传感器访问也需要 HTTPS,i
1304
1812
 
1305
1813
  ### MouseControls
1306
1814
 
1307
- 见上文默认项。通过 `controls.mouse` 或渲染子组件 `<MouseControls />` 覆盖。
1815
+ 通过 `controls.mouse` 或渲染子组件 `<MouseControls />` 覆盖默认通道。自行渲染子组件时**不会**继承 `controls.fovSpeed`,需要的话请直接传到组件上。
1816
+
1817
+ - `enabled` — 默认 `true`。
1818
+ - `rotateSpeed` — 默认 `0.35`(相对位移缩放;`0` 关闭旋转)。
1819
+ - `zoomSpeed` — 默认 `0.08`;滚轮实际步进为 `deltaY * zoomSpeed`。
1820
+ - `wheel` — 默认 `true`。
1821
+ - `invert` — 默认 `false`;可覆盖共享 `controls.invert`。
1822
+ - `buttons` — 默认 `["left"]`;取值 `"left" | "middle" | "right"`。
1823
+ - `fovSpeed` — 省略则不封顶;有值时把单次滚轮步进夹到 `±fovSpeed`(不是按秒限速)。
1308
1824
 
1309
1825
  ### TouchControls
1310
1826
 
1311
- 单指拖拽与可选双指缩放(`pinchZoom`,默认 `true`)。通过 `controls.touch` 或渲染 `<TouchControls />` 覆盖。
1827
+ 单指拖拽与可选双指缩放。通过 `controls.touch` 或渲染 `<TouchControls />` 覆盖。
1828
+
1829
+ - `enabled` — 默认 `true`。
1830
+ - `rotateSpeed` — 默认 `0.35`,语义同鼠标。
1831
+ - `invert` — 默认 `false`。
1832
+ - `pinchZoom` — 默认 `true`。捏合按两指距离比改 FOV,不受 `fovSpeed` 限制。
1312
1833
 
1313
1834
  ### KeyboardControls
1314
1835
 
1315
- 按住方向键(或自定义绑定)连续环视 / FOV;`0` 重置;可选场景绑定。默认:`rotateSpeed` `60`,`zoomSpeed` `30`,`shiftMultiplier` `3`。`invert` 仅翻转上下方向。通过 `controls.keyboard` 或渲染 `<KeyboardControls />` 覆盖。
1836
+ 按住方向键(或自定义绑定)连续环视 / FOV。通过 `controls.keyboard` 或渲染 `<KeyboardControls />` 覆盖。自行渲染时同样不会继承 `controls.fovSpeed`。
1837
+
1838
+ - `enabled` — 默认 `true`。
1839
+ - `rotateSpeed` / `zoomSpeed` — 默认 `60` / `30`(度/秒)。
1840
+ - `shiftMultiplier` — 默认 `3`。
1841
+ - `invert` — 默认 `false`,仅翻转上下。
1842
+ - `fovSpeed` — 省略不封顶;有值时按 `min(zoom, fovSpeed * dt)` 限制。
1843
+ - `keys` — 按动作整组替换;未写出的动作保留默认;空数组等于解绑该动作。匹配时同时比较 `event.key` 与 `event.code`(均转小写)。
1844
+ - `onPreviousScene` / `onNextScene` — 有绑定时按键仍会 `preventDefault`;无回调则不换场景。
1316
1845
 
1317
1846
  `controls` 上跨模式共享选项:`enabled`、`invert`(鼠标/触控拖拽方向)、`bouncingLimits`、`fovSpeed`、`frictionStop`(默认 `0.01`),以及既有阻尼 / 惯性选项。
1318
1847
 
@@ -1365,7 +1894,7 @@ export function KeyboardExample({ scenes }: { scenes: Scene[] }) {
1365
1894
 
1366
1895
  按住移动与缩放键以度/秒连续运动(`rotateSpeed` / `zoomSpeed`)。按住 Shift 乘以倍率(`shiftMultiplier`,默认 `3`)。场景与重置绑定每次按键触发一次。画布须获得焦点才能接收按键(先点击查看器)。
1367
1896
 
1368
- 默认绑定:方向键与 WASD 环视,`+/-` 调 FOV,`0` 重置;提供回调时 `[`/`PageUp` 与 `]`/`PageDown` 为上一/下一场景。`MouseControls` 与 `TouchControls` 同样可用子组件覆盖。仅当需要关闭某通道时使用 `controls.mouse={false}` / `controls.touch={false}` / `controls.keyboard={false}`。
1897
+ 默认绑定:方向键与 WASD 环视;放大用 `+` 与 `=`,缩小用 `-` 与 `_`;`0` 重置;`[` / `PageUp` 与 `]` / `PageDown` 为上一/下一场景(无回调时不换场景,但仍可能拦截按键)。`MouseControls` 与 `TouchControls` 同样可用子组件覆盖。仅当需要关闭某通道时使用 `controls.mouse={false}` / `controls.touch={false}` / `controls.keyboard={false}`。
1369
1898
 
1370
1899
  ## 全景坐标事件
1371
1900
 
@@ -1432,18 +1961,20 @@ import {
1432
1961
 
1433
1962
  支持的回调(适用处标注 krpano 名称):
1434
1963
 
1435
- - 视图:`onViewChange`(`onviewchange`)、`onViewSettled`、`onViewInteractionStart` / `onViewInteractionEnd`
1436
- - 全景外壳上的指针(非热点):`onClick`、`onDoubleClick`、`onPointerDown`、`onPointerUp`、`onPointerMove`、`onContextMenu`
1437
- - `onWheel` — 仅鼠标滚轮;触控捏合不会合成 wheel 事件
1964
+ - 视图:`onViewChange`(`onviewchange`)、`onViewSettled` — 参数均为 `{ yaw, pitch, fov }`。视角变化时发 `viewchange`;惯性/补间首次停稳或指令动画结束时发 `viewsettled`。
1965
+ - `onViewInteractionStart` / `onViewInteractionEnd` — `{ source }`,`source` 为 `"mouse" | "touch" | "keyboard"`。
1966
+ - 全景外壳上的指针(非热点):`onClick`、`onDoubleClick`、`onPointerDown`、`onPointerUp`、`onPointerMove`、`onContextMenu` — 参数为 `PanoramaPointerEvent`(`position`、`nativeEvent`、`button`、`buttons`、`clientX`、`clientY`、可选 `pointerId`)。
1967
+ - `onWheel` — 仅鼠标滚轮;触控捏合不会合成 wheel 事件。参数 `{ delta, nativeEvent }`,`delta` 与 `WheelEvent.deltaY` 同号。
1438
1968
  - 空闲:`onIdle` / `onIdleEnd`,每实例 `idleTime`(毫秒,默认 `2000`)
1439
- - 全屏:`onEnterFullscreen` / `onExitFullscreen`
1440
- - `onResize` — 通过 `ResizeObserver` 获取画布 content-box 尺寸
1441
- - 自动旋转:`onAutoRotateStart` / `onAutoRotateStop` / `onAutoRotateOneRound`
1442
- - 陀螺仪:`onGyroAvailable` / `onGyroUnavailable` / `onGyroEnable` / `onGyroDisable` / `onGyroDenied`
1969
+ - 全屏:`onEnterFullscreen` / `onExitFullscreen`(无参数)
1970
+ - `onResize` — `{ width, height }`,来自画布 content-box 的 `ResizeObserver`
1971
+ - 自动旋转:`onAutoRotateStart` / `onAutoRotateStop` / `onAutoRotateOneRound`(无参数)
1972
+ - 陀螺仪:`onGyroAvailable` / `onGyroUnavailable` / `onGyroEnable` / `onGyroDisable` / `onGyroDenied`(无参数)
1973
+ - VR:`onVRAvailable` / `onVRUnavailable` / `onVREnter` / `onVRExit` / `onVRDenied` / `onVRUnknownDevice`。进入与退出携带模式 `"webxr" | "mobilevr" | "fake"`;`onVRDenied` 可带可选 `error`。
1443
1974
 
1444
- `PanoViewer` 现有的 `onViewChange` / `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` props 仍受支持,共用同一事件总线。
1975
+ `PanoViewer` 现有的 `onViewChange` / `onPanoramaClick` / `onPanoramaDoubleClick` / `onPanoramaPointerMove` props 仍受支持,共用同一事件总线。`usePanoEvents` 与 `<PanoEvents>` 接受同一组 props,hook 无返回值。
1445
1976
 
1446
- 资源加载回调由各组件提供:`Sphere` 使用 `onLoad`、`onError` 和 `onPreviewError`;`Tile` / `SphereTile` 使用 `onLoadProgress`、`onTileError` 和 `onPreviewError`。各源通过 `onReady` 报告首次可绘制状态;`Scenes` 通过 `onSceneReady`、`onTransitionEnd` 和 `onTransitionError` 报告场景状态。本包不逐一实现 krpano 的 XML 加载、VR 和逐帧渲染事件。
1977
+ 资源加载回调由各组件提供:`Sphere` 使用 `onLoad`、`onError` 和 `onPreviewError`;`Tile` / `SphereTile` 使用 `onLoadProgress`、`onTileError` 和 `onPreviewError`。各源通过 `onReady` 报告首次可绘制状态;`Scenes` 通过 `onSceneReady`、`onTransitionEnd` 和 `onTransitionError` 报告场景状态。本包不逐一实现 krpano 的 XML 加载与逐帧渲染事件;VR 生命周期请使用上方的 `onVR*`。
1447
1978
 
1448
1979
  ## PanoContextMenu
1449
1980
 
@@ -1530,15 +2061,32 @@ import {
1530
2061
  </PanoViewer>
1531
2062
  ```
1532
2063
 
1533
- 使用 `{ preset: "fullscreen", label: "…" }` 可覆盖预设菜单项的显示文案。在 prop 外完全手动组装时(例如自定义 `PanoContextMenu` 子组件),调用 `createPanoContextMenuPresets({ reset, toggleFullscreen, isFullscreen })` 并使用 `.resetView` / `.fullscreen` / `.defaults`。
2064
+ 使用 `{ preset: "fullscreen", label: "…" }` 可覆盖预设菜单项的显示文案;预设还可覆盖 `id`、`className`、`style`、`disabled`、`hidden`。在 prop 外完全手动组装时(例如自定义 `PanoContextMenu` 子组件),调用 `createPanoContextMenuPresets({ reset, toggleFullscreen, isFullscreen })` 并使用 `.resetView` / `.fullscreen` / `.defaults`。
2065
+
2066
+ 具体项可使用 `icon` React 节点或 `image` URL(可用 `imageAlt`)。条目还可设 `disabled`、`hidden`、`className`、`style`。`onSelect` 上下文含 `position`、`view`、`nativeEvent` 与 `close()`。分隔线始终来自 `"separator"` 预设。
1534
2067
 
1535
- 具体项可使用 `icon` React 节点或 `image` URL。分隔线始终来自 `"separator"` 预设。外观涵盖背景、文字色、边框、圆角、阴影、仅背景的 `opacity`(从 `MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY` / `0.4` 到 `1` 钳制;标签与图标保持不透明)、悬停/禁用色与图标尺寸。若需半透明填充而不使用 `opacity`,仍可传 `background: "rgba(...)"`。
2068
+ 传入 `items` 时会忽略 `prepend` / `append`。`children` 为完全自定义渲染:`(props: { event, view, close }) => ReactNode`,优先于 `items`;打开、关闭与定位仍由组件处理。也可用 `className`、`style`、`itemClassName`、`separatorClassName`。
2069
+
2070
+ 外观字段与默认值:`background` `#161616`,`color` `#f4f4f4`,`border` `"1px solid #2e2e2e"`,`borderRadius` `8`,`shadow` `"0 8px 24px rgba(0,0,0,0.45)"`,`opacity` `1`(仅背景,钳制到 `[0.4, 1]`;标签与图标保持不透明),`fontSize` `13`,`minWidth` `180`,`padding` `4`,`iconSize` `16`,`itemPadding` `"6px 10px"`,`itemHoverBackground` `#2a2a2a`,`itemDisabledColor` `#777777`,`separatorColor` `#333333`,`separatorMargin` `"4px 6px"`。若需半透明填充而不使用 `opacity`,仍可传 `background: "rgba(...)"`。
1536
2071
 
1537
2072
  完全自定义 DOM 菜单时,设 `contextMenu={false}` 并渲染 `PanoContextMenu` 子组件。不要同时挂载默认菜单与子 `PanoContextMenu`。避免与 `controls.mouse.buttons: ["right"]` 同时使用 — 两者都会占用右键。
1538
2073
 
1539
2074
  ## 共享热点约定与可序列化定义
1540
2075
 
1541
- 每个热点有 `id`,以及可选的 `visible`、`interactive`、`pointerEvents`、`renderOrder` 与语义交互回调。点热点此外还使用受控 `position`、角度 `width`/`height`、热点 `mode` 与下文缩放选项。`interactive={false}` 保持热点可见,同时让绘制工具接收其下方的全景指针事件。`pointerEvents="none"` 忽略鼠标、触控与笔,事件穿透到全景,热点仍可键盘交互。可交互热点悬停时画布光标为 `pointer`;若设置了热点 `cursor` 则使用该值。
2076
+ 每个热点有 `id`,以及可选的共享字段与语义交互回调。点热点此外还使用受控 `position`、角度 `width`/`height`、热点 `mode` 与下文缩放选项。`interactive={false}` 保持热点可见,同时让绘制工具接收其下方的全景指针事件。`pointerEvents="none"` 忽略鼠标、触控与笔,事件穿透到全景,热点仍可键盘交互。可交互热点悬停时画布光标为 `pointer`;若设置了热点 `cursor` 则使用该值。
2077
+
2078
+ 共享字段默认与用法:
2079
+
2080
+ - `visible` — 默认 `true`。
2081
+ - `renderOrder` — 点热点默认 `0`;多边形 / 折线默认 `10`。
2082
+ - `opacity` — 默认 `1`;作用于点热点纹理 / iframe 等。
2083
+ - `selected` — 默认 `false`;为 `true` 时画出约 3px 选中框(创作态常用)。
2084
+ - `interactive` — 默认 `true`;`pointerEvents` 默认 `"auto"`。
2085
+ - `onHoverChange(hovered, event)` — 第一个参数为布尔悬停态,第二个为交互事件。
2086
+ - `onClick` — 位置与输入源(`"pointer"` 或 `"keyboard"`)。
2087
+ - 点热点拖拽:`draggable` 开启后,`onDragStart` 在开始拖动时触发,`onPositionChange` 在移动中报告新位置(宿主须写回状态),`onDragEnd` 在释放时触发。多边形 / 折线的同名回调携带顶点集合,见对应组件节。
2088
+
2089
+ 各点热点省略 `width` / `height` 时的默认角尺寸:`ImageHotspot` / `SequenceHotspot` `12×8`;画布 `GraphicHotspot` `10×10`(`kind: "svg"` 走图像路径时为 `12×8`);`VideoHotspot` `16×9`;`AudioHotspot` `8×8`;`TextHotspot` `16×6`;`IframeHotspot` `18×12`。
1542
2090
 
1543
2091
  ```tsx
1544
2092
  <TextHotspot
@@ -1549,7 +2097,7 @@ import {
1549
2097
  />
1550
2098
  ```
1551
2099
 
1552
- `onClick` 与 `onHoverChange` 接收位置与输入源(`"pointer"` 或 `"keyboard"`)。点热点可使用 `draggable`、`onDragStart`、`onPositionChange`、`onDragEnd`;宿主须将报告的位置写回状态。多边形与折线热点通过 `onVerticesChange` 报告受控 `vertices`。
2100
+ 点热点可使用 `draggable`、`onDragStart`、`onPositionChange`、`onDragEnd`;宿主须将报告的位置写回状态。多边形与折线热点通过 `onVerticesChange` 报告受控 `vertices`。
1553
2101
 
1554
2102
  每个热点可在锚点显示 DOM tooltip。将字符串或 `{ text?, image?, imageAlt? }` 作为 `tooltip` 传入。默认 `tooltipTrigger` 为 `"always"`:有内容且热点可见时气泡常驻屏幕。点击热点不会切换显示。可选 `tooltipTrigger="hover"` 在指针悬停或键盘焦点时显示;`"click"` 固定显示直至再次点击热点或点击空白全景。hover 与 click 触发需要 `interactive` 且 `pointerEvents` 不为 `"none"`。
1555
2103
 
@@ -1610,7 +2158,7 @@ const hotspots: HotspotDefinition[] = [
1610
2158
  ];
1611
2159
  ```
1612
2160
 
1613
- 定义仅含数据。在宿主中用 switch 渲染,应用特定的点击行为、受控媒体状态与错误报告属于宿主职责。
2161
+ 定义仅含数据。在宿主中用 switch 渲染,应用特定的点击行为、受控媒体状态与错误报告属于宿主职责。项目 JSON 里的 `label` 不是组件 prop;`ProjectScenes` 会用 `hotspot.label || hotspot.id` 作为无障碍名称(`ariaLabel`)。`selected` 只存在于运行时 props,不会写入定义。
1614
2162
 
1615
2163
  ## ImageHotspot
1616
2164
 
@@ -1636,11 +2184,11 @@ import { ImageHotspot, PanoViewer, Sphere } from "@ericchen1990/pano-view";
1636
2184
 
1637
2185
  设 `draggable` 并在 `onPositionChange` 中更新受控位置以移动选中热点。可点击热点需要 `ariaLabel`:PanoViewer 会创建内部语义控件以支持 Tab、Enter 与 Space,并显示 WebGL 焦点轮廓。
1638
2186
 
1639
- `ImageHotspot` 调用 `onLoad(texture)` 与 `onError(error)`。
2187
+ `ImageHotspot` 调用 `onLoad(texture)` 与 `onError(error)`。省略尺寸时默认 `width={12}`、`height={8}`。
1640
2188
 
1641
2189
  ## GraphicHotspot
1642
2190
 
1643
- `GraphicHotspot` 接受内置 `circle`、`triangle`、`diamond`、`star`、`arrow`、`rectangle`、`ring` 图形、SVG URL,或带显式 viewBox 的安全 SVG path 数据。内置图形支持 `fill`、`stroke`、`strokeWidth`;矩形支持相对 `cornerRadius`(`0` 到 `0.5`);环支持 `innerRadius`。
2191
+ `GraphicHotspot` 接受内置 `circle`、`triangle`、`diamond`、`star`、`arrow`、`rectangle`、`ring` 图形、SVG URL,或带显式 viewBox 的安全 SVG path 数据。内置图形支持 `fill`、`stroke`、`strokeWidth`;矩形支持相对 `cornerRadius`(`0` 到 `0.5`);环支持 `innerRadius`(相对外半径的比例,默认 `0.58`)。画布图形的 `strokeWidth` 是 **512 纹理**上的线宽(默认 `8`),与多边形屏幕像素线宽不同。`kind: "path"` 需要 `path` 与 `viewBox: [x, y, width, height]` 四元组。
1644
2192
 
1645
2193
  ```tsx
1646
2194
  import { GraphicHotspot } from "@ericchen1990/pano-view";
@@ -1683,7 +2231,7 @@ import { GraphicHotspot } from "@ericchen1990/pano-view";
1683
2231
 
1684
2232
  `cornerRadius` 相对于渲染图形较短边:`0` 为直角,`0.5` 为最大圆角。该值为相对比例,不使用纹理像素作为单位。
1685
2233
 
1686
- `GraphicHotspot` 与 `ImageHotspot` 相同,使用 `onLoad(texture)` 与 `onError(error)`。内置 path 本地栅格化;仅接受 SVG URL 或 SVG `path` 加显式 `viewBox`,不接受任意 SVG 标记。
2234
+ `GraphicHotspot` 与 `ImageHotspot` 相同,使用 `onLoad(texture)` 与 `onError(error)`。内置 path 本地栅格化;仅接受 SVG URL 或 SVG `path` 加显式 `viewBox`,不接受任意 SVG 标记。画布图形默认角尺寸 `10×10`;`kind: "svg"` 走图像加载时默认 `12×8`。
1687
2235
 
1688
2236
  ## 热点模式与缩放行为
1689
2237
 
@@ -1741,6 +2289,8 @@ import { PolygonHotspot } from "@ericchen1990/pano-view";
1741
2289
 
1742
2290
  `strokeWidth` 为屏幕空间 CSS 像素,FOV 变化时视觉一致。`strokeDashSize` 与 `strokeGapSize` 同为 CSS 像素;省略或设 `strokeDashSize` 为 `0` 为实线。拖拽整体平移所有顶点,同时归一化 yaw、钳制 pitch。单顶点编辑由创作工作流提供,非运行时热点组件职责。
1743
2291
 
2292
+ 默认:`fill` `#df6b42`,`fillOpacity` `0.34`,`stroke` `#f5fbfc`,`strokeWidth` `2`,`strokeOpacity` `1`,`renderOrder` `10`。`onDragStart` / `onDragEnd` / `onVerticesChange` 的载荷是顶点事件(`vertices`、`startVertices`、`position`、`startPosition`),不是点热点的位置拖拽事件。`onInvalid` 的 `code` 为 `too_few_vertices`、`self_intersection`、`contains_pole`、`outside_hemisphere`。
2293
+
1744
2294
  设 `fillOpacity={0}` 可得仅轮廓闭合多边形。填充与轮廓共用相同球面边界采样,使轮廓沿填充区域的边界绘制。
1745
2295
 
1746
2296
  ## PolylineHotspot
@@ -1764,11 +2314,11 @@ import { PolylineHotspot } from "@ericchen1990/pano-view";
1764
2314
  />;
1765
2315
  ```
1766
2316
 
1767
- `onInvalid` 仅报告一种无效运行时形状:少于两个顶点。
2317
+ `onInvalid` 仅报告一种无效运行时形状:少于两个顶点(`code: "too_few_vertices"`)。默认 `stroke` `#f5fbfc`、`strokeWidth` `2`、`strokeOpacity` `1`、`renderOrder` `10`。拖拽回调同样携带顶点集合(`PolylineVerticesChangeEvent`)。
1768
2318
 
1769
2319
  ## SequenceHotspot
1770
2320
 
1771
- `SequenceHotspot` 动画播放精灵图:等尺寸帧组成的竖向或横向条带。与常见 krpano 动画热点格式一致。`width` 与 `height` 应使用单帧宽高比,而非整条带。`playing` 由宿主受控;`fps` 默认 `12`,`loop` 默认 `true`。
2321
+ `SequenceHotspot` 动画播放精灵图:等尺寸帧组成的竖向或横向条带。与常见 krpano 动画热点格式一致。`width` 与 `height` 应使用单帧宽高比,而非整条带。`playing` 由宿主受控;`fps` 默认 `12`,`loop` 默认 `true`,`frameDirection` 默认 `"vertical"`。省略尺寸时默认 `12×8`。
1772
2322
 
1773
2323
  ```tsx
1774
2324
  import { SequenceHotspot } from "@ericchen1990/pano-view";
@@ -1790,11 +2340,11 @@ import { SequenceHotspot } from "@ericchen1990/pano-view";
1790
2340
  />;
1791
2341
  ```
1792
2342
 
1793
- 序列加载使用 `onLoadProgress` 与 `onError`。组件不会自行改变 `playing`:请在 UI、点击处理、`onEnded` 或播放错误中更新该 prop。
2343
+ 序列加载使用 `onLoadProgress`(`{ id, total, loaded, failed }`)与 `onError`(`{ id, src, error }`)。另有 `onFrameChange`(`{ id, index, src }`)与 `onPlaybackStateChange`(`"playing" | "paused" | "ended"`,无 `"blocked"`)。组件不会自行改变 `playing`:请在 UI、点击处理、`onEnded` 或播放错误中更新该 prop。
1794
2344
 
1795
2345
  ## VideoHotspot
1796
2346
 
1797
- `VideoHotspot` 使用 `HTMLVideoElement` 与 `VideoTexture`。其 `playing` prop 同样受控:在点击处理或应用状态中切换。源变更或卸载时元素暂停并释放纹理。`playsInline` 默认 `true`,并使用与 `PanoVideo` 相同的内联播放属性。元素会隐藏挂在查看器内,以便移动端浏览器把它留在页面中。`muted` 默认 `true`,`loop` 默认 `false`,`preload` 默认 `"metadata"`。
2347
+ `VideoHotspot` 使用 `HTMLVideoElement` 与 `VideoTexture`。其 `playing` prop 同样受控:在点击处理或应用状态中切换。源变更或卸载时元素暂停并释放纹理。`playsInline` 默认 `true`,并使用与 `PanoVideo` 相同的内联播放属性。元素会隐藏挂在查看器内,以便移动端浏览器把它留在页面中。`muted` 默认 `true`,`loop` 默认 `false`,`preload` 默认 `"metadata"`(另可 `"none"` | `"auto"`),`volume` 默认 `1`(0–1)。省略尺寸时默认 `16×9`。非同源资源可设 `crossOrigin`(`""` | `"anonymous"` | `"use-credentials"`)。
1798
2348
 
1799
2349
  ```tsx
1800
2350
  import { VideoHotspot } from "@ericchen1990/pano-view";
@@ -1820,7 +2370,7 @@ import { VideoHotspot } from "@ericchen1990/pano-view";
1820
2370
 
1821
2371
  浏览器可能拒绝非静音或非手势播放。此时 `onPlaybackStateChange` 收到 `"blocked"`,`onPlaybackError` 收到浏览器错误;宿主仍控制 `playing` 并提供显式用户操作。
1822
2372
 
1823
- 视频通过 `onError` 报告媒体与 poster 失败。与 `SequenceHotspot` 相同,不会自行改变 `playing`,以保证源变更、卸载与 React StrictMode 生命周期可预测。
2373
+ 视频通过 `onError` 报告媒体与 poster 失败,参数含 `source: "poster" | "video"`。与 `SequenceHotspot` 相同,不会自行改变 `playing`,以保证源变更、卸载与 React StrictMode 生命周期可预测。
1824
2374
 
1825
2375
  ## AudioHotspot
1826
2376
 
@@ -1853,11 +2403,11 @@ import { AudioHotspot } from "@ericchen1990/pano-view";
1853
2403
  />;
1854
2404
  ```
1855
2405
 
1856
- 与其他媒体热点相同,`AudioHotspot` 不会自行改变 `playing`。
2406
+ 与其他媒体热点相同,`AudioHotspot` 不会自行改变 `playing`。省略标记尺寸时默认 `8×8`。也可设 `crossOrigin`;加载失败走 `onError({ id, error })`,与 `onPlaybackError` 不同。
1857
2407
 
1858
2408
  ## TextHotspot
1859
2409
 
1860
- `TextHotspot` 将纯文本栅格化到 canvas 纹理。不接受 HTML 或 Markdown。`\n` 为硬换行;`whiteSpace="normal"`(默认)在热点角向宽度内自动换行。尺寸与其他点热点一样为度。排版使用与 CSS 相同的名称(`fontFamily`、`fontSize`、`fontWeight`、`fontStyle`),但绘制在 canvas 上,非 DOM 样式。`fontSize` 为 canvas 像素(默认 96),不随面板高度缩放。`fontWeight` 默认 `600`,`fontStyle` 默认 `"normal"`。`padding` 与 `borderRadius` 仍为纹理较短边的比例,故仍随 `width` / `height` 变化。
2410
+ `TextHotspot` 将纯文本栅格化到 canvas 纹理。不接受 HTML 或 Markdown。`\n` 为硬换行;`whiteSpace="normal"`(默认)在热点角向宽度内自动换行,也可 `"nowrap"`。尺寸与其他点热点一样为度,默认面板 `16×6`。排版使用与 CSS 相同的名称(`fontFamily`、`fontSize`、`fontWeight`、`fontStyle`),但绘制在 canvas 上,非 DOM 样式。`fontSize` 为 canvas 像素(默认 96),不随面板高度缩放。`fontWeight` 默认 `600`,`fontStyle` 默认 `"normal"`。`align` 默认 `"center"`(`left` | `center` | `right`),`verticalAlign` 默认 `"middle"`(`top` | `middle` | `bottom`)。`color` 默认 `#f8fafc`;`background` 默认 `#111827`(空字符串跳过底板);`backgroundOpacity` 默认 `0.72`。`padding` 默认 `0.08`,`borderRadius` 默认 `0.08`(相对较短边,`0`–`0.5`)。`lineHeight` 默认 `1.25`(相对字号的倍数)。`stroke` / `strokeWidth` 在 `strokeWidth > 0` 时描边(默认宽度 `0`)。
1861
2411
 
1862
2412
  ```tsx
1863
2413
  import { TextHotspot } from "@ericchen1990/pano-view";
@@ -1904,7 +2454,7 @@ import { IframeHotspot } from "@ericchen1990/pano-view";
1904
2454
  />;
1905
2455
  ```
1906
2456
 
1907
- `pointerPolicy="hotspot"`(默认)将 iframe 设为 `pointer-events: none`,点击与拖拽作用于 WebGL 平面。`"content"` 使页面可交互并阻止穿透拖拽。热点 `pointerEvents="none"` 也会强制 iframe 叠加层不捕获指针。`sandbox` 默认为 `allow-scripts allow-popups allow-forms`,不含 `allow-same-origin`。`referrerPolicy` 默认为 `strict-origin-when-cross-origin`。优先同源文档;许多第三方站点发送 `X-Frame-Options` 无法加载。
2457
+ `pointerPolicy="hotspot"`(默认)将 iframe 设为 `pointer-events: none`,点击与拖拽作用于 WebGL 平面。`"content"` 使页面可交互并阻止穿透拖拽。热点 `pointerEvents="none"` 也会强制 iframe 叠加层不捕获指针。`sandbox` 默认为 `allow-scripts allow-popups allow-forms`,不含 `allow-same-origin`。`referrerPolicy` 默认为 `strict-origin-when-cross-origin`。另可设 `title`(省略时用 `ariaLabel`)、`allow`、`loading`(`"eager"` | `"lazy"`)、`background`,以及 `onLoad` / `onError`。省略尺寸时默认 `18×12`。优先同源文档;许多第三方站点发送 `X-Frame-Options` 无法加载。
1908
2458
 
1909
2459
  ## Next.js 与 SSR
1910
2460