@ericchen1990/pano-view 2.0.0 → 2.2.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 +147 -43
- package/README.zh-CN.md +1111 -0
- package/dist/index.cjs +137 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +31 -7
- package/dist/index.d.ts +31 -7
- package/dist/index.mjs +176 -144
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +339 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @ericchen1990/pano-view
|
|
2
2
|
|
|
3
|
+
**English** | [简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
3
5
|
Composable React components for equirectangular and krpano-style multiresolution panorama viewers.
|
|
4
6
|
|
|
5
7
|
Try the components in the [live playground](https://pano-view-playground.vercel.app/).
|
|
@@ -7,36 +9,68 @@ Try the components in the [live playground](https://pano-view-playground.vercel.
|
|
|
7
9
|
## krpano-compatible tile output
|
|
8
10
|
|
|
9
11
|
Use this package when you want a React-native viewer around equirectangular
|
|
10
|
-
images or an existing krpano cube-tile pyramid.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
images or an existing krpano cube-tile pyramid. Copy the krpano `<cube>` `url`
|
|
13
|
+
and `multires` attributes into `Tile`'s `urlTemplate` and `multires`, and copy
|
|
14
|
+
`<preview url>` into `previewUrl`; krpano's on-disk naming is not the same as
|
|
15
|
+
this package's defaults, so omitting them loads the wrong tiles or preview.
|
|
16
|
+
Rendering, controls, hotspots, and scene transitions stay in your React
|
|
17
|
+
application. This package is compatible with that tile output format but is
|
|
18
|
+
not affiliated with krpano.
|
|
14
19
|
|
|
15
20
|
## Install
|
|
16
21
|
|
|
22
|
+
### Package
|
|
23
|
+
|
|
17
24
|
```bash
|
|
18
25
|
npm install @ericchen1990/pano-view
|
|
19
26
|
```
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
pnpm and Yarn work the same way (`pnpm add` / `yarn add`).
|
|
29
|
+
|
|
30
|
+
### Peer dependencies
|
|
31
|
+
|
|
32
|
+
These packages must be installed in the **host application**, not nested under
|
|
33
|
+
this library. npm 7+ and pnpm install them automatically; Yarn Classic and npm
|
|
34
|
+
6 do not, so install them explicitly.
|
|
35
|
+
|
|
36
|
+
| Package | Supported range |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `react` | `>=19.0.0 <19.3.0` |
|
|
39
|
+
| `react-dom` | `>=19.0.0 <19.3.0` |
|
|
40
|
+
| `three` | `^0.185.1` |
|
|
41
|
+
| `@react-three/fiber` | `^9.7.0` |
|
|
42
|
+
| `@react-three/drei` | `^10.0.0` |
|
|
43
|
+
| `@react-three/xr` | `^6.6.0` |
|
|
44
|
+
|
|
45
|
+
Keep a **single copy** of React, Three.js, and the React Three Fiber packages
|
|
46
|
+
in the host app. Duplicate `three` trees are a common cause of blank canvases
|
|
47
|
+
and context errors (`R3F: Hooks can only be used within the Canvas component`).
|
|
48
|
+
If that happens, check `npm ls three` / `pnpm why three` and dedupe.
|
|
49
|
+
|
|
50
|
+
React 19.3 and later are outside the supported range.
|
|
51
|
+
|
|
52
|
+
### Stylesheet (HTML chrome)
|
|
53
|
+
|
|
54
|
+
Import the shipped stylesheet once in the host app. The WebGL panorama
|
|
55
|
+
(`PanoViewer`, `Sphere`, `Tile`, 3D hotspots) renders without it. Built-in
|
|
56
|
+
**HTML** overlays do not: they use prefixed `pano-*` classes for layout.
|
|
25
57
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
Those overlays include video controls, captions, the default context menu,
|
|
59
|
+
hotspot tooltips, accessibility-only chrome, and WebVR session UI.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import "@ericchen1990/pano-view/styles.css";
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or from CSS:
|
|
30
66
|
|
|
31
67
|
```css
|
|
32
|
-
@import "
|
|
33
|
-
@source "../node_modules/@ericchen1990/pano-view";
|
|
68
|
+
@import "@ericchen1990/pano-view/styles.css";
|
|
34
69
|
```
|
|
35
70
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
component appearance props.
|
|
71
|
+
Skip this import only if you do not use those overlays, or if you restyle
|
|
72
|
+
them entirely through `className`, `style`, and appearance props. Without
|
|
73
|
+
the stylesheet, the overlays still mount, but they render unstyled.
|
|
40
74
|
|
|
41
75
|
## Exported components
|
|
42
76
|
|
|
@@ -91,6 +125,7 @@ Angles are public degrees. Positive yaw looks right and positive pitch looks up.
|
|
|
91
125
|
|
|
92
126
|
```tsx
|
|
93
127
|
import { useRef } from "react";
|
|
128
|
+
import "@ericchen1990/pano-view/styles.css";
|
|
94
129
|
import {
|
|
95
130
|
PanoViewer,
|
|
96
131
|
Sphere,
|
|
@@ -120,7 +155,7 @@ export function ControlledExample() {
|
|
|
120
155
|
style={{ height: 560 }}
|
|
121
156
|
>
|
|
122
157
|
<AutoRotate enabled speed={18} acceleration={18} startDelay={1_000} />
|
|
123
|
-
<Sphere src="/panoramas/room.webp" />
|
|
158
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
124
159
|
</PanoViewer>
|
|
125
160
|
</>
|
|
126
161
|
);
|
|
@@ -151,6 +186,12 @@ The canvas cursor defaults to `grab`, becomes `grabbing` while dragging with the
|
|
|
151
186
|
|
|
152
187
|
`Sphere` expects a 2:1 equirectangular image. Use `yawOffset` when the source's forward direction needs horizontal adjustment.
|
|
153
188
|
|
|
189
|
+
`previewUrl` is required and shows a low-resolution 2:1 image while `src` loads.
|
|
190
|
+
Relative paths resolve against the directory of `src`; root-absolute and
|
|
191
|
+
`http(s)` URLs are used as-is. For krpano sphere scenes, copy
|
|
192
|
+
[`<preview url>`](https://krpano.com/docu/xml/#preview) into `previewUrl`. That
|
|
193
|
+
preview must be equirectangular, not a cube strip.
|
|
194
|
+
|
|
154
195
|
```tsx
|
|
155
196
|
"use client";
|
|
156
197
|
|
|
@@ -159,7 +200,10 @@ import { PanoViewer, Sphere } from "@ericchen1990/pano-view";
|
|
|
159
200
|
export function SphereExample() {
|
|
160
201
|
return (
|
|
161
202
|
<PanoViewer style={{ width: "100%", height: 560 }}>
|
|
162
|
-
<Sphere
|
|
203
|
+
<Sphere
|
|
204
|
+
src="/panoramas/room.webp"
|
|
205
|
+
previewUrl="preview.webp"
|
|
206
|
+
/>
|
|
163
207
|
</PanoViewer>
|
|
164
208
|
);
|
|
165
209
|
}
|
|
@@ -240,13 +284,13 @@ that are not same-origin should set `crossOrigin` (usually `"anonymous"`).
|
|
|
240
284
|
|
|
241
285
|
## Tile
|
|
242
286
|
|
|
243
|
-
`Tile` renders six inward-facing cube faces and loads only tiles around the current view.
|
|
287
|
+
`Tile` renders six inward-facing cube faces and loads only tiles around the current view. When `urlTemplate` is omitted, paths relative to `baseUrl` use this package default:
|
|
244
288
|
|
|
245
289
|
```text
|
|
246
|
-
tiles/
|
|
290
|
+
tiles/%s/l%l/%v/l%l_%s_%h_%v.webp
|
|
247
291
|
```
|
|
248
292
|
|
|
249
|
-
Faces are `f`, `r`, `b`, `l`, `u`, and `d`; rows and columns are 1-based.
|
|
293
|
+
which expands to `tiles/{face}/l{level}/{row}/l{level}_{face}_{col}_{row}.webp`. Faces are `f`, `r`, `b`, `l`, `u`, and `d`; rows and columns are 1-based.
|
|
250
294
|
|
|
251
295
|
```tsx
|
|
252
296
|
import { PanoViewer, Tile } from "@ericchen1990/pano-view";
|
|
@@ -257,33 +301,81 @@ export function TileExample() {
|
|
|
257
301
|
<Tile
|
|
258
302
|
baseUrl="https://cdn.example.com/panoramas/room"
|
|
259
303
|
multires="512,500,1000,2000"
|
|
304
|
+
previewUrl="previews/cube-vertical.webp"
|
|
260
305
|
/>
|
|
261
306
|
</PanoViewer>
|
|
262
307
|
);
|
|
263
308
|
}
|
|
264
309
|
```
|
|
265
310
|
|
|
266
|
-
|
|
311
|
+
krpano cube-tile output does not follow that default. Read `url`, `multires`,
|
|
312
|
+
and the preview path from the krpano XML
|
|
313
|
+
([`<cube>`](https://krpano.com/docu/xml/#image.cube),
|
|
314
|
+
[`<preview>`](https://krpano.com/docu/xml/#preview)) and pass them through. If
|
|
315
|
+
those values are missing or do not match the files on disk, tiles resolve to
|
|
316
|
+
the wrong face, level, or row/column and the panorama looks scrambled.
|
|
317
|
+
|
|
318
|
+
| krpano | `Tile` / `TileScene` prop |
|
|
319
|
+
|---|---|
|
|
320
|
+
| `<cube url>` | `urlTemplate` |
|
|
321
|
+
| `<cube multires>` | `multires` |
|
|
322
|
+
| `<preview url>` | `previewUrl` |
|
|
323
|
+
| `<preview striporder>` | `previewFaceOrder` |
|
|
324
|
+
|
|
325
|
+
`Sphere` uses the same `<preview url>` → `previewUrl` mapping; that file must be a 2:1 equirectangular image, not a cube strip.
|
|
326
|
+
|
|
327
|
+
krpano's documented short syntax:
|
|
328
|
+
|
|
329
|
+
```xml
|
|
330
|
+
<preview url="preview.jpg" />
|
|
331
|
+
<image>
|
|
332
|
+
<cube url="pano_%s_%l_%v_%h.jpg"
|
|
333
|
+
multires="512,1024,2048,4096"
|
|
334
|
+
/>
|
|
335
|
+
</image>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
maps to:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
<Tile
|
|
342
|
+
baseUrl="/panoramas/room"
|
|
343
|
+
urlTemplate="pano_%s_%l_%v_%h.jpg"
|
|
344
|
+
multires="512,1024,2048,4096"
|
|
345
|
+
previewUrl="preview.jpg"
|
|
346
|
+
/>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
A MAKE PANO (MULTIRES) folder layout is often
|
|
350
|
+
`tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg` with `preview.jpg` at the scene root. That
|
|
351
|
+
filename is `%v_%h` (row then column). This package's default tile template is
|
|
352
|
+
`%h_%v` (column then row). Copy the XML values instead of assuming the two
|
|
353
|
+
conventions match.
|
|
354
|
+
|
|
355
|
+
The first `multires` value is the tile size. Remaining values are ascending cube-face sizes for `l1`, `l2`, and later levels. `previewUrl` is required. Relative paths resolve against `baseUrl`; root-absolute and `http(s)` URLs are used as-is. A typical krpano MAKE PANO (MULTIRES) scene looks like this:
|
|
267
356
|
|
|
268
357
|
```tsx
|
|
269
358
|
<Tile
|
|
270
359
|
baseUrl="/panoramas/room"
|
|
360
|
+
urlTemplate="tiles/%s/l%l/%v/l%l_%s_%v_%h.jpg"
|
|
271
361
|
multires="512,1000,2000"
|
|
272
|
-
previewUrl="
|
|
362
|
+
previewUrl="preview.jpg"
|
|
273
363
|
previewFaceOrder={["l", "f", "r", "b", "u", "d"]}
|
|
274
364
|
/>
|
|
275
365
|
```
|
|
276
366
|
|
|
277
|
-
`previewFaceOrder` must list the six face codes in their top-to-bottom atlas order: `f`, `r`, `b`, `l`, `u`, and `d`.
|
|
367
|
+
`previewFaceOrder` must list the six face codes in their top-to-bottom atlas order: `f`, `r`, `b`, `l`, `u`, and `d`. The preview image is a vertical 1×6 cubestrip. krpano `thumb.jpg` is a small thumbnail and is not this atlas.
|
|
278
368
|
|
|
279
369
|
During rapid rotation or zoom, loaded tiles remain visible while newly visible tiles use their parent level or the preview as a local fallback.
|
|
280
370
|
|
|
281
|
-
|
|
371
|
+
For layouts that are not krpano XML, override the default with a placeholder
|
|
372
|
+
template or a resolver. Both return paths relative to `baseUrl`:
|
|
282
373
|
|
|
283
374
|
```tsx
|
|
284
375
|
<Tile
|
|
285
376
|
baseUrl="/panoramas/room"
|
|
286
377
|
multires={{ tileSize: 512, levels: [500, 1000, 2000] }}
|
|
378
|
+
previewUrl="previews/cube-vertical.webp"
|
|
287
379
|
urlTemplate="assets/%s/%l/%v_%h.webp"
|
|
288
380
|
resolveTileUrl={({ face, level, row, col }) =>
|
|
289
381
|
`api/tile/${face}/${level}/${row}/${col}`
|
|
@@ -304,7 +396,7 @@ a single non-stereo cube panorama.
|
|
|
304
396
|
## Scenes
|
|
305
397
|
|
|
306
398
|
`Scenes` switches controlled sphere and cube-tile scenes with GPU-only
|
|
307
|
-
snapshot blending. The target scene loads its sphere
|
|
399
|
+
snapshot blending. The target scene loads its sphere or tile preview first;
|
|
308
400
|
once ready, the current framebuffer becomes a temporary GPU texture, its source
|
|
309
401
|
textures are released, and the target scene blends in. This avoids holding two
|
|
310
402
|
high-resolution tile scenes in WebGL memory at once.
|
|
@@ -317,12 +409,14 @@ import {
|
|
|
317
409
|
} from "@ericchen1990/pano-view";
|
|
318
410
|
|
|
319
411
|
const scenes: Scene[] = [
|
|
320
|
-
{ id: "lobby", type: "sphere", src: "/panoramas/lobby.webp" },
|
|
412
|
+
{ id: "lobby", type: "sphere", src: "/panoramas/lobby.webp", previewUrl: "preview.webp" },
|
|
321
413
|
{
|
|
322
414
|
id: "terrace",
|
|
323
415
|
type: "tile",
|
|
324
416
|
baseUrl: "/panoramas/terrace",
|
|
325
417
|
multires: "512,1000,2000",
|
|
418
|
+
urlTemplate: "tiles/%s/l%l/%v/l%l_%s_%v_%h.webp",
|
|
419
|
+
previewUrl: "previews/cube-vertical.webp",
|
|
326
420
|
},
|
|
327
421
|
];
|
|
328
422
|
|
|
@@ -368,7 +462,7 @@ to leave the source unfiltered.
|
|
|
368
462
|
```tsx
|
|
369
463
|
<PanoViewer style={{ height: 560 }}>
|
|
370
464
|
<PanoFilter preset="pencil" intensity={0.85} />
|
|
371
|
-
<Sphere src="/panoramas/room.webp" />
|
|
465
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
372
466
|
<ImageHotspot
|
|
373
467
|
id="door"
|
|
374
468
|
position={{ yaw: 24, pitch: -6 }}
|
|
@@ -402,7 +496,7 @@ Auto-rotation is off by default. Render `AutoRotate` inside `PanoViewer` to enab
|
|
|
402
496
|
```tsx
|
|
403
497
|
<PanoViewer style={{ height: 560 }}>
|
|
404
498
|
<AutoRotate enabled speed={12} acceleration={6} startDelay={2_000} />
|
|
405
|
-
<Sphere src="/panoramas/room.webp" />
|
|
499
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
406
500
|
</PanoViewer>
|
|
407
501
|
```
|
|
408
502
|
|
|
@@ -451,7 +545,7 @@ export function GyroExample() {
|
|
|
451
545
|
touchMode="full"
|
|
452
546
|
onDenied={() => setEnabled(false)}
|
|
453
547
|
/>
|
|
454
|
-
<Sphere src="/panoramas/room.webp" />
|
|
548
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
455
549
|
</PanoViewer>
|
|
456
550
|
</>
|
|
457
551
|
);
|
|
@@ -508,7 +602,7 @@ export function WebVRExample() {
|
|
|
508
602
|
mobileVr
|
|
509
603
|
onEnterVR={(mode) => console.log("Entered", mode)}
|
|
510
604
|
/>
|
|
511
|
-
<Sphere src="/panoramas/room.webp" />
|
|
605
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
512
606
|
</PanoViewer>
|
|
513
607
|
);
|
|
514
608
|
}
|
|
@@ -551,7 +645,7 @@ interactive through the default WebXR controller rays.
|
|
|
551
645
|
}}
|
|
552
646
|
style={{ height: 560 }}
|
|
553
647
|
>
|
|
554
|
-
<Sphere src="/panoramas/room.webp" />
|
|
648
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
555
649
|
</PanoViewer>
|
|
556
650
|
```
|
|
557
651
|
|
|
@@ -649,7 +743,7 @@ export function PlacementExample() {
|
|
|
649
743
|
onPanoramaPointerMove={({ position }) => console.log("move", position)}
|
|
650
744
|
style={{ height: 560 }}
|
|
651
745
|
>
|
|
652
|
-
<Sphere src="/panoramas/room.webp" />
|
|
746
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
653
747
|
</PanoViewer>
|
|
654
748
|
);
|
|
655
749
|
}
|
|
@@ -690,7 +784,7 @@ import {
|
|
|
690
784
|
onAutoRotateOneRound={() => console.log("full turn")}
|
|
691
785
|
/>
|
|
692
786
|
<AutoRotate enabled />
|
|
693
|
-
<Sphere src="/panoramas/room.webp" />
|
|
787
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
694
788
|
</PanoViewer>;
|
|
695
789
|
```
|
|
696
790
|
|
|
@@ -723,7 +817,7 @@ separator between them.
|
|
|
723
817
|
|
|
724
818
|
```tsx
|
|
725
819
|
<PanoViewer style={{ height: 560 }}>
|
|
726
|
-
<Sphere src="/panoramas/room.webp" />
|
|
820
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
727
821
|
</PanoViewer>
|
|
728
822
|
```
|
|
729
823
|
|
|
@@ -731,7 +825,7 @@ Disable the default menu (restore the browser menu, or mount your own):
|
|
|
731
825
|
|
|
732
826
|
```tsx
|
|
733
827
|
<PanoViewer contextMenu={false}>
|
|
734
|
-
<Sphere src="/panoramas/room.webp" />
|
|
828
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
735
829
|
</PanoViewer>
|
|
736
830
|
```
|
|
737
831
|
|
|
@@ -739,7 +833,7 @@ Tune appearance while keeping the default items:
|
|
|
739
833
|
|
|
740
834
|
```tsx
|
|
741
835
|
<PanoViewer contextMenu={{ appearance: { opacity: 0.92, borderRadius: 8 } }}>
|
|
742
|
-
<Sphere src="/panoramas/room.webp" />
|
|
836
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
743
837
|
</PanoViewer>
|
|
744
838
|
```
|
|
745
839
|
|
|
@@ -766,7 +860,7 @@ only add your own:
|
|
|
766
860
|
}}
|
|
767
861
|
style={{ height: 560 }}
|
|
768
862
|
>
|
|
769
|
-
<Sphere src="/panoramas/room.webp" />
|
|
863
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
770
864
|
</PanoViewer>
|
|
771
865
|
```
|
|
772
866
|
|
|
@@ -800,7 +894,7 @@ reimplementing fullscreen enter/exit state:
|
|
|
800
894
|
}}
|
|
801
895
|
style={{ height: 560 }}
|
|
802
896
|
>
|
|
803
|
-
<Sphere src="/panoramas/room.webp" />
|
|
897
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
804
898
|
</PanoViewer>
|
|
805
899
|
```
|
|
806
900
|
|
|
@@ -931,7 +1025,7 @@ are angular degrees, so they remain independent of the canvas resolution.
|
|
|
931
1025
|
import { ImageHotspot, PanoViewer, Sphere } from "@ericchen1990/pano-view";
|
|
932
1026
|
|
|
933
1027
|
<PanoViewer style={{ height: 560 }}>
|
|
934
|
-
<Sphere src="/panoramas/room.webp" />
|
|
1028
|
+
<Sphere src="/panoramas/room.webp" previewUrl="preview.webp" />
|
|
935
1029
|
<ImageHotspot
|
|
936
1030
|
id="gallery"
|
|
937
1031
|
ariaLabel="Open gallery"
|
|
@@ -1260,4 +1354,14 @@ not load.
|
|
|
1260
1354
|
|
|
1261
1355
|
## Next.js and SSR
|
|
1262
1356
|
|
|
1263
|
-
The distributed entry is marked as a client module. Render it below a Client
|
|
1357
|
+
The distributed entry is marked as a client module. Render it below a Client
|
|
1358
|
+
Component boundary. Panorama resources are loaded in the browser; no API keys
|
|
1359
|
+
or server configuration are required.
|
|
1360
|
+
|
|
1361
|
+
Import the stylesheet once from a Client Component or a global CSS file:
|
|
1362
|
+
|
|
1363
|
+
```ts
|
|
1364
|
+
import "@ericchen1990/pano-view/styles.css";
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
See [Stylesheet (HTML chrome)](#stylesheet-html-chrome).
|