Panorama
Import
Section titled “Import”Panorama is exported from a subpath (not the main barrel) so it isn’t bundled into apps that don’t use it:
import Panorama from '@delightstack/components/panorama';import type { PanoramaHotspot } from '@delightstack/components';Peer Dependency
Section titled “Peer Dependency”Panorama requires three as a peer dependency (loaded lazily at runtime):
pnpm add threeBasic Usage
Section titled “Basic Usage”View code
<script> import Panorama from '@delightstack/components/panorama';
</script>
<Panorama src="/panoramas/mountain-360.jpg" />Examples
Section titled “Examples”Auto-Rotate
Section titled “Auto-Rotate”Enable gentle continuous rotation. Rotation pauses when the user interacts and resumes after 3 seconds of inactivity.
View code
<Panorama src="/panoramas/cityscape.jpg" auto_rotate auto_rotate_speed={1.5} />Custom Initial View
Section titled “Custom Initial View”Start the viewer facing a specific direction.
<Panorama src="/panoramas/interior.jpg" initial_view={{ pitch: 10, yaw: 90 }} fov={60}/>Interactive Hotspots
Section titled “Interactive Hotspots”Add clickable markers at specific positions within the panorama.
Click a labelled hotspot.
View code
<script> import Panorama from '@delightstack/components/panorama'; import type { PanoramaHotspot } from '@delightstack/components';
const hotspots: PanoramaHotspot[] = [ { position: { pitch: 5, yaw: 45 }, label: 'Living Room' }, { position: { pitch: -10, yaw: 180 }, label: 'Kitchen' }, { position: { pitch: 0, yaw: 270 }, label: 'Garden', data: { room_id: 3 } }, ];
</script>
<Panorama src="/panoramas/house.jpg" {hotspots} onhotspotclick={({ hotspot }) => console.log('Clicked', hotspot.label)} />Static Fallback
Section titled “Static Fallback”Provide a fallback image for browsers without WebGL support.
<Panorama src="/panoramas/beach.jpg" fallback="/panoramas/beach-preview.jpg"/>Skeleton Loading
Section titled “Skeleton Loading”The skeleton is loading-state-driven: it shows while the equirectangular texture is still downloading and dismisses itself the moment the panorama is ready.
View code
<Panorama skeleton src={panorama_url} />| Prop | Type | Default | Description |
|---|---|---|---|
src | string | required | Equirectangular panorama image URL |
initial_view | { pitch: number, yaw: number } | { pitch: 0, yaw: 0 } | Starting camera orientation in degrees |
fov | number | 75 | Field of view in degrees (30-120) |
auto_rotate | boolean | false | Enable gentle continuous rotation |
auto_rotate_speed | number | 1 | Rotation speed multiplier |
show_controls | boolean | true | Show zoom, fullscreen, and reset buttons |
interactive | boolean | true | Enable drag, touch, and scroll interaction |
gyroscope | boolean | false | Enable device orientation control on mobile |
hotspots | PanoramaHotspot[] | [] | Interactive markers in the panorama |
fallback | string | undefined | Static image URL when WebGL is unavailable |
skeleton | boolean | false | Show loading skeleton |
id | string | auto | Element ID |
class | string | '' | Additional CSS classes |
element | HTMLElement | undefined | Bindable reference to the root element ($bindable) |
Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
onviewchange | { pitch: number, yaw: number, fov: number } | Fires when the camera orientation changes (throttled) |
onhotspotclick | { hotspot: PanoramaHotspot } | Fires when a hotspot is clicked |
onload | none | Fires when the panorama is ready |
onerror | { error: Error } | Fires when loading fails |
PanoramaHotspot
Section titled “PanoramaHotspot”interface PanoramaHotspot { position: { pitch: number; yaw: number }; label?: string; data?: Record<string, unknown>;}Accessibility
Section titled “Accessibility”- Container has
role="application"witharia-label="360 degree panorama viewer" tabindex="0"for keyboard focus- Arrow keys pan the view, +/- zoom in/out
- Home resets the view to the initial position
- Space toggles auto-rotation pause
- Hotspot buttons have
aria-labelset to the hotspot label or “Hotspot N” - Control buttons (zoom in, zoom out, fullscreen, reset) have descriptive
aria-label - Focus-visible outlines on all interactive elements
- Auto-rotation respects
prefers-reduced-motion - Pauses rendering when the element is not visible (IntersectionObserver)