Self-Hosting Tours¶
Host your own 360° VR experiences using the @xrgallery/viewer npm package.
Installation¶
Quick Start¶
Create an index.html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My VR Tour</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; }
#app { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/@xrgallery/viewer/dist/viewer-bundle.iife.js"></script>
<script>
XRGallery.create({
element: '#app',
config: {
experience: {
title: "My Virtual Tour"
},
stages: [
{
id: "lobby",
name: "Welcome",
skybox: {
type: "image",
url: "./panoramas/lobby.jpg"
},
hotspots: []
}
]
}
});
</script>
</body>
</html>
That's it! Open the file in a browser to view your tour.
Using a scene from XRGallery cloud?
You can also pass a sceneId instead of a config object:
Framework integration
For React, Vue, Angular, Svelte, and Next.js examples, see the Framework Guides.
Configuration Reference¶
Experience Settings¶
experience: {
title: "Museum Tour", // Tour title (required)
description: "Virtual museum", // Optional description
defaultFOV: 1.0 // Camera field of view (0.3-2.5, default: 1.0)
}
You can also add global audio that plays across all stages:
{
experience: { ... },
stages: [ ... ],
globalAudio: {
url: "https://example.com/background-music.mp3",
volume: 0.3,
loop: true
}
}
Stages¶
Each stage is a 360° location in your tour:
{
id: "gallery-1", // Unique ID (required)
name: "Main Gallery", // Display name (required)
skybox: { ... }, // Background (required)
hotspots: [ ... ], // Interactive elements (optional)
models: [ ... ], // 3D models (optional)
planes: [ ... ], // 2D images/videos in space (optional)
lights: [ ... ], // Custom lighting (optional)
audioUrl: "https://...", // Ambient audio URL (optional)
audioVolume: 0.5, // 0-1 ambient audio volume (optional)
initialCameraTarget: { x, y, z } // Where camera looks on load (optional)
}
Skybox Types¶
Hotspot Types¶
Info Hotspot¶
Displays information when clicked:
{
id: "info-1",
type: "info",
position: { x: 0, y: 1.6, z: -5 },
label: "Learn More",
color: "#4A90E2",
infoTitle: "About This Artwork",
infoDescription: "This piece was created in 1920...",
infoImageUrl: "https://example.com/detail.jpg", // Optional
// Optional: Add links or embed content
linkUrl: "https://example.com",
linkText: "Visit Website",
videoUrl: "https://example.com/video.mp4",
iframeUrl: "https://example.com/embed"
}
Navigation Hotspot¶
Links to another stage:
{
id: "nav-1",
type: "navigation",
position: { x: 3, y: 0, z: -4 },
label: "Go to Gallery 2",
targetStageId: "gallery-2",
// Portal customization
portalEffectStyle: "enhanced", // "clean", "enhanced", "minimal", "alien", "floor-arrow"
portalColor: "#4A90E2",
portalRestSize: 1.0,
portalHoverSize: 1.2
}
Audio Hotspot¶
Plays audio when clicked:
{
id: "audio-1",
type: "audio",
position: { x: -2, y: 1.5, z: -3 },
label: "Audio Guide",
audioUrl: "https://example.com/narration.mp3",
audioVolume: 0.8,
audioLoop: false,
// 3D spatial audio settings
audioSpatial: true,
audioRolloffFactor: 1,
audioMaxDistance: 100
}
Combined Hotspot (Info + Audio)¶
Shows information AND plays audio:
{
id: "both-1",
type: "both",
position: { x: 1, y: 1.6, z: -4 },
label: "Guided Info",
infoTitle: "The Starry Night",
infoDescription: "Vincent van Gogh, 1889...",
audioUrl: "https://example.com/starry-night-narration.mp3"
}
Lead Capture Hotspot¶
Collect visitor information with customizable forms:
{
id: "contact-1",
type: "lead_capture",
position: { x: 0, y: 1.5, z: -5 },
label: "Get in Touch",
leadForm: {
formType: "contact",
title: "Contact Us",
fields: [
{ name: "name", type: "text", label: "Name", required: true },
{ name: "email", type: "email", label: "Email", required: true },
{ name: "message", type: "textarea", label: "Message" }
]
}
}
Ambient Audio¶
Add background music or ambience to a stage:
{
id: "forest",
name: "Forest Trail",
skybox: { ... },
audioUrl: "https://example.com/forest-ambience.mp3",
audioVolume: 0.5 // 0 to 1
}
3D Models¶
Add glTF/GLB models to your scenes:
{
id: "gallery",
name: "Gallery",
skybox: { ... },
models: [
{
url: "https://example.com/sculpture.glb",
position: { x: 0, y: 0, z: -5 },
rotation: { x: 0, y: 1.57, z: 0 }, // Radians
scale: 2.0
}
]
}
2D Planes¶
Embed images or videos as floating planes in 3D space:
{
planes: [
{
type: "image",
url: "https://example.com/poster.jpg",
position: { x: 0, y: 2, z: -6 },
scale: { width: 3, height: 4 }
},
{
type: "video",
url: "https://example.com/promo.mp4",
position: { x: 4, y: 1.5, z: -5 },
scale: { width: 4, height: 2.25 },
autoplay: true,
loop: true
}
]
}
Custom Lighting¶
Override the default scene lighting:
{
lights: [
{
type: "hemispheric",
intensity: 0.7,
diffuse: "#FFFFFF",
groundColor: "#444444",
direction: { x: 0, y: 1, z: 0 }
},
{
type: "point",
position: { x: 0, y: 5, z: 0 },
intensity: 1.5,
range: 20
}
]
}
Navigation Overlays¶
Add floor plans or maps with markers:
Complete Example¶
XRGallery.create({
element: '#app',
config: {
experience: {
title: "Art Gallery Virtual Tour",
description: "Explore our collection from anywhere"
},
stages: [
{
id: "entrance",
name: "Gallery Entrance",
skybox: {
type: "image",
url: "./images/entrance-360.jpg"
},
initialCameraTarget: { x: 0, y: 0, z: -100 },
hotspots: [
{
id: "welcome",
type: "info",
position: { x: 0, y: 1.6, z: -4 },
label: "Welcome",
infoTitle: "Welcome to the Gallery",
infoDescription: "Explore our virtual exhibition."
},
{
id: "to-modern",
type: "navigation",
position: { x: 3, y: 0, z: -3 },
label: "Modern Art Wing",
targetStageId: "modern-wing",
portalEffectStyle: "enhanced"
},
{
id: "to-classical",
type: "navigation",
position: { x: -3, y: 0, z: -3 },
label: "Classical Art Wing",
targetStageId: "classical-wing"
}
]
},
{
id: "modern-wing",
name: "Modern Art",
skybox: {
type: "video",
url: "./videos/modern-art-360.mp4"
},
audioUrl: "./audio/jazz-ambient.mp3",
audioVolume: 0.3,
models: [
{
url: "./models/sculpture.glb",
position: { x: 0, y: 0, z: -4 },
scale: 1.5
}
],
hotspots: [
{
id: "mondrian-info",
type: "both",
position: { x: 2, y: 1.5, z: -4 },
label: "Mondrian",
infoTitle: "Composition with Red, Blue, and Yellow",
infoDescription: "Piet Mondrian, 1930.",
infoImageUrl: "./images/mondrian-detail.jpg",
audioUrl: "./audio/mondrian-guide.mp3"
},
{
id: "back-to-entrance",
type: "navigation",
position: { x: -4, y: 0, z: 2 },
label: "Back to Entrance",
targetStageId: "entrance"
}
]
},
{
id: "classical-wing",
name: "Classical Art",
skybox: {
type: "image",
url: "./images/classical-360.jpg"
},
hotspots: [
{
id: "back-to-entrance-2",
type: "navigation",
position: { x: 4, y: 0, z: 2 },
label: "Back to Entrance",
targetStageId: "entrance"
}
]
}
],
navigation: {
type: "floorplan",
showMinimap: true,
floorPlans: [
{ floor: 1, name: "Gallery", imageUrl: "./images/floorplan.png" }
],
markers: [
{ stageId: "entrance", label: "Entrance", floorPlanPosition: { x: 0.5, y: 0.2 } },
{ stageId: "modern-wing", label: "Modern", floorPlanPosition: { x: 0.8, y: 0.5 } },
{ stageId: "classical-wing", label: "Classical", floorPlanPosition: { x: 0.2, y: 0.5 } }
]
}
}
});
Positioning Hotspots¶
Hotspot positions use 3D coordinates relative to the camera center:
- x: Left (-) / Right (+)
- y: Down (-) / Up (+)
- z: Behind (+) / In Front (-)
Positioning Tips
- Place navigation hotspots at
y: 0to appear at floor level - Info hotspots work well at
y: 1.5toy: 1.8(eye level) - Keep hotspots at distance 3-6 units from center (
z: -3toz: -6) - Use the XRGallery Editor to visually place hotspots
Hosting Options¶
Static File Hosting¶
The viewer works with any static file host:
- Netlify: Drop your folder to deploy
- Vercel:
vercel deploy - GitHub Pages: Push to
gh-pagesbranch - AWS S3: Upload to S3 bucket with static hosting
- Any web server: Nginx, Apache, etc.
CORS Requirements¶
If your media files are hosted on a different domain, ensure CORS headers are set:
See CORS Troubleshooting for details.
Exporting from XRGallery¶
The easiest way to create a self-hosted tour:
- Create your tour at xrgallery.online
- Use the visual editor to add stages and hotspots
- Click Export in the dashboard
- Download the ZIP file
- Extract and host anywhere
The exported package includes:
index.html- Ready-to-use viewerconfig.json- Your tour configurationREADME.md- Instructions
Browser Support¶
| Browser | Version | Notes |
|---|---|---|
| Chrome | 79+ | Full WebXR support |
| Firefox | 79+ | Full WebXR support |
| Safari | 15.4+ | WebXR support |
| Edge | 79+ | Full WebXR support |
| Mobile Chrome | 79+ | WebXR on supported devices |
| Mobile Safari | 15.4+ | Limited WebXR |
VR Headset Support¶
Any WebXR-compatible device:
- Meta Quest ⅔/Pro
- HTC Vive / Vive Pro
- Valve Index
- Windows Mixed Reality
- Pico 4
- And more...
VR Mode
VR mode requires HTTPS in production. For local development, localhost works without HTTPS.
Bundle Size¶
The viewer bundle is approximately 3.1 MB gzipped and includes:
- Babylon.js 3D engine
- HLS.js for video streaming
- All UI components
- WebXR support
The bundle is fully self-contained with no external dependencies at runtime.
Further Reading¶
- Configuration Schema Reference - Complete TypeScript reference
- Hotspots Guide - Deep dive into hotspot features
- Navigation System - Floor plans and maps