v2.6.0

<bmx-panorama-viewer>

A 360-degree picture you look around in, and tours of several joined by hotspots: estate agents' and hotels' virtual tours, venues, construction progress, museums, training sites, campus visits. Drag, swipe, use the keys or move the phone to look around; zoom with the wheel, a pinch or the keys; hotspots go to the next scene (with a short move through), show a note or open a link; a floor plan shows where you stand and which way you face, and a strip lists the scenes. Pictures that do not cover the whole sphere (a 180-degree panorama, say) are kept within their edges.

13 properties · 6 events · 7 methods · 5 parts

Example

Drag to look around, or focus the view and use the arrow keys. The glowing hotspots walk you through the house; the others show notes. The floor plan shows where you stand and which way you face.

Show markup
<bmx-panorama-viewer id="ex-pano" style="--bmx-panorama-viewer-height: 34rem"></bmx-panorama-viewer>
<p class="note" id="ex-pano-out" role="status" style="margin: 12px 0 0">Drag to look around, or focus the view and use the arrow keys. The glowing hotspots walk you through the house; the others show notes. The floor plan shows where you stand and which way you face.</p>
<label style="display: inline-flex; gap: 8px; align-items: center; margin-block-start: 8px"><input type="checkbox" id="ex-pano-edit" /> Edit the tour (double-click to add a hotspot, drag one to move it)</label>

<script type="module">
  await customElements.whenDefined('bmx-panorama-viewer');
  const viewer = document.getElementById('ex-pano');
  const out = document.getElementById('ex-pano-out');
  const base = 'assets/panorama/';
  const scene = (id, title, view, mapAt, hotspots, caption) => ({
    id,
    title,
    caption,
    src: `${base}${id}.jpg`,
    preview: `${base}${id}-preview.jpg`,
    thumb: `${base}${id}-thumb.jpg`,
    north: 0,
    ...view,
    map: mapAt,
    hotspots,
  });
  // A fictional show home: a lounge, a kitchen through the archway, and the garden through the glass doors.
  // Hotspot directions are yaw (degrees clockwise from north, which is the middle of each picture) and pitch.
  viewer.tour = {
    title: '14 Orchard Lane',
    start: 'lounge',
    map: { src: `${base}plan.svg`, width: 400, height: 420, title: 'Ground floor and garden' },
    scenes: [
      scene('lounge', 'Lounge', { yaw: 20, pitch: -4, fov: 100 }, { x: 160, y: 356 }, [
        { id: 'to-kitchen', target: 'kitchen', yaw: 71.6, pitch: -9.9, title: 'Kitchen', targetYaw: 90 },
        { id: 'to-garden', target: 'garden', yaw: 0, pitch: -11, title: 'Garden', targetYaw: 160 },
        { id: 'shelves', yaw: -52.6, pitch: -7.8, title: 'Built-in shelving', text: 'Solid oak, floor to ceiling, with concealed lighting on each shelf.' },
        { id: 'art', yaw: 174.8, pitch: 1.3, title: 'Artwork', text: 'Shown for styling; not included in the sale.' },
        { id: 'spec', yaw: -84.3, pitch: 0, title: 'Window specification', href: 'https://example.com/orchard-lane/specification' },
      ], 'The lounge, with glass doors to the garden.'),
      scene('kitchen', 'Kitchen', { yaw: 90, pitch: -6, fov: 100 }, { x: 260, y: 340 }, [
        { id: 'to-lounge', target: 'lounge', yaw: -84.3, pitch: -15.3, title: 'Lounge', targetYaw: 200 },
        { id: 'island', yaw: 180, pitch: -29.8, title: 'Quartz island', text: 'Seats four, with power sockets set into the end panel.' },
        { id: 'tiles', yaw: 90, pitch: -9.9, title: 'Hand-glazed tiles', text: 'Splashback in a sea-green glaze, made locally.' },
      ]),
      scene('garden', 'Garden', { yaw: 160, pitch: -2, fov: 100 }, { x: 160, y: 190 }, [
        { id: 'to-lounge-2', target: 'lounge', yaw: 180, pitch: -6.2, title: 'Back inside', targetYaw: 0 },
        { id: 'tree', yaw: -39.3, pitch: 7.2, title: 'Mature oak', text: 'About forty years old; protected, so it stays.' },
        { id: 'patio', yaw: 134.3, pitch: -16, title: 'Sandstone patio', text: 'About 30 square metres, laid in 2024.' },
      ]),
    ],
  };
  viewer.addEventListener('bmxSceneChange', e => (out.textContent = `bmxSceneChange: ${e.detail.previous ?? '-'} → ${e.detail.scene}.`));
  viewer.addEventListener('bmxHotspotClick', e => (out.textContent = `bmxHotspotClick: ${e.detail.hotspot.title} (${e.detail.hotspot.type}).`));
  viewer.addEventListener('bmxChange', e => (out.textContent = `bmxChange: ${e.detail.reason} ${e.detail.hotspot} in ${e.detail.scene}. Save e.detail.tour to keep it.`));
  document.getElementById('ex-pano-edit').addEventListener('change', e => (viewer.editable = e.target.checked));
</script>
<bmx-panorama-viewer src="harbour-360.jpg"></bmx-panorama-viewer>
<bmx-panorama-viewer id="tour"></bmx-panorama-viewer>
<script>
  tour.tour = { start: 'hall', scenes: [{ id: 'hall', src: 'hall.jpg', hotspots: [{ target: 'kitchen', yaw: 80, pitch: -10, title: 'Kitchen' }] }, ...] };
</script>

Drawn with WebGL from the picture itself (no sphere mesh, no library); a small preview shows first while the full picture loads. With editable the page can author a tour: double-click to add a hotspot, drag to move it, edit its title, note, type and target, and receive the whole tour in bmxChange.

Accessibility: the view is focusable and turned with the arrow keys, and the direction faced is announced; every hotspot is a button in the tab order (clockwise from the start), and focusing one turns the view to it; the floor plan's scenes, the scene strip and every tool are labelled buttons.

Properties

PropertyAttributeTypeDefaultDescription
autoRotate auto-rotate number 0 Degrees a second the view turns by itself when left alone; 0 never. Off when the reader prefers reduced motion, unless they turn it on.
autoRotateDelay auto-rotate-delay number 4 Seconds of stillness before turning by itself starts again.
controls controls boolean true Show the tools.
editable editable boolean false Hotspots can be added, moved, edited and deleted; changes arrive in bmxChange.
label label string — The viewer's accessible name. Default: the tour's title, else "360° view".
maxFov max-fov number 120
minFov min-fov number 30 The narrowest and widest the view zooms, in degrees.
scene scene string '' The scene showing. Mutable.
showMap show-map boolean true Show the floor plan (when the tour has one). Mutable.
showScenes show-scenes boolean true Show the strip of scenes (when there is more than one). Mutable.
src src string — One equirectangular picture: the quick way to show a single panorama.
strings strings Partial<BmxPanoramaViewerStrings> | string — Words to show instead of the English ones, as an object or JSON.
tour tour BmxPanoTour | Record<string, unknown> | unknown[] | string — A tour: { title, start, scenes, map }, an array of scenes, or JSON of either. Each scene: id, title, src, preview, thumb, yaw, pitch, fov, north, haov, vaov, vOffset, hotspots, map: { x, y }.

Events

EventDetailDescription
bmxChange BmxPanoChangeDetail Fired when an editable tour changes, with the whole tour.
bmxError BmxPanoLoadDetail Fired when a scene's picture cannot be loaded.
bmxHotspotClick BmxPanoHotspotDetail Fired when a hotspot is chosen. Cancel to handle it yourself.
bmxLoad BmxPanoLoadDetail Fired when a scene's full picture has loaded.
bmxSceneChange BmxPanoSceneChangeDetail Fired when another scene shows.
bmxViewChange BmxPanoViewChangeDetail Fired as the view turns or zooms, at most five times a second, and when it comes to rest.

Methods

MethodSignatureDescription
capture capture(type?: string, quality?: number) => Promise<Blob | null> A picture of the current view (PNG unless another type is asked for).
getTour getTour() => Promise<BmxPanoTour> The tour as it is now (with any edits).
getView getView() => Promise<BmxPanoView> Where the reader is looking now.
goToScene goToScene(id: string, view?: Partial<BmxPanoView>) => Promise<void> Goes to another scene, facing view (else its own starting view).
lookAt lookAt(yaw: number, pitch?: number, fov?: number) => Promise<void> Turns to face a yaw and pitch (and zooms to fov, if given).
setFocus setFocus() => Promise<void> Puts keyboard focus on the view.
setView setView(view: Partial<BmxPanoView>, animate?: boolean) => Promise<void> Turns to a view (animated unless animate is false).

CSS shadow parts

PartDescription
hotspot A hotspot; also hotspot-scene, hotspot-info, hotspot-link.
map The floor plan.
scenes The strip of scenes.
toolbar The tools.
viewport The view itself.

CSS custom properties

PropertyDescription
--bmx-panorama-viewer-accent Hotspots, the plan's "you are here" and pressed tools.
--bmx-panorama-viewer-glass The background of the floating controls.
--bmx-panorama-viewer-height The viewer's height.
--bmx-panorama-viewer-on-glass Text and icons on the floating controls.
--bmx-panorama-viewer-radius Corner radius of the viewer.