A framework-agnostic equirectangular image player built with Three.js, TypeScript, and Vite.
- Mouse, touch, pinch, inertia, zoom controls, compass, and WebXR.
- Runtime color filters and configurable nadir covers.
- Typed hotspots for links, quizzes, clues, locked content, and products.
- Branded and MLS-friendly unbranded rendering modes.
- Local canvas snapshots and high-resolution server snapshot requests.
- Serializable player state and ZIP export for offline self-hosting.
- React wrapper with typed callbacks.
- GPL-3.0 licensed.
This player renders whatever equirectangular image URL you give it — you still need somewhere to upload, store, and serve the actual panorama files. Mirame360 is a free 360° photo/video hosting platform built by the same team: upload once, get optimized web-ready URLs and secure embeds, and drop them straight into this player (or use the Mirame360 WordPress plugin if your site runs WordPress).
npm install 360-image-player threeReact is an optional peer dependency.
import { Image360Player } from '360-image-player';
const player = new Image360Player({
container: document.getElementById('viewer')!,
imageUrl: [
'https://example.com/panorama.webp',
'https://example.com/panorama.jpeg',
],
showControls: true,
compass: true,
initialView: { yaw: 20, pitch: 0, hfov: 80 },
nadir: {
imageUrl: 'https://example.com/logo.png',
radius: 55,
},
});
player.addHTMLOverlay({
id: 'product-1',
type: 'product',
yaw: 45,
pitch: 5,
product: {
id: 'chair-1',
title: 'Chair',
price: '$99',
vendor: 'shopify',
},
});
player.on('addtocart', ({ product }) => {
// Delegate the actual cart mutation to Shopify, PrestaShop, or the host app.
console.log(product);
});imageUrl accepts either one URL or an ordered list. When a source cannot be
loaded or decoded, the player tries the next URL and emits error only after
all configured sources have failed. Existing integrations using a string do
not need to change.
player.setView({ yaw: 30, pitch: -5, hfov: 70 });
const viewport = player.getView();
// Optional low-cost motion for visible previews.
player.startAutoRotate(2);
player.setRenderingActive(false); // suspend GPU work while offscreen
player.setRenderingActive(true);
player.stopAutoRotate();
// Browser-resolution PNG from the current canvas.
const localBlob = await player.takeSnapshot();
// High-resolution image generated by the Mirame360 backend.
const result = await player.requestSnapshot({
endpoint: `/api/media/${mediaId}/snapshot/`,
width: 4096,
height: 2304,
format: 'jpeg',
headers: { Authorization: `Bearer ${token}` },
});The backend endpoint queues an FFmpeg v360 task and the player polls its authenticated status URL until the generated storage URL is available.
player.addHTMLOverlay({
id: 'question-1',
type: 'quiz',
yaw: 10,
pitch: 0,
title: 'Which door is correct?',
quizChoices: [
{ id: 'left', label: 'Left' },
{ id: 'right', label: 'Right', correct: true },
],
unlocks: ['exit-door'],
});
player.addHTMLOverlay({
id: 'exit-door',
type: 'locked',
yaw: 90,
pitch: 0,
requires: ['exit-door'],
text: 'Exit',
});
player.setGameState(savedState);
const stateToPersist = player.getGameState();Use brandingMode: 'unbranded' to hide hotspots unless they explicitly set branded: false. External URL opening can also be disabled with allowExternalLinks: false.
Custom hotspot HTML is sanitized by default. Supply sanitizeHTML to integrate a stricter application sanitizer.
const zip = await player.exportOffline({
playerScriptUrl: '/assets/360-image-player.standalone.umd.min.js',
fetchAssets: true,
});The ZIP contains the standalone player, panorama, optional nadir image, config.json, and a ready-to-host index.html. Remote assets must allow CORS.
| Option | Default | Description |
|---|---|---|
imageUrl |
required | Panorama URL or ordered list of primary and fallback URLs. |
autoLoad |
true |
Load the panorama immediately. |
showControls |
true |
Render zoom and reset controls. |
compass |
false |
Render a heading compass. |
initialView |
{ yaw: 0, pitch: 0, hfov: 90 } |
Initial viewport. |
brandingMode |
branded |
Hide branded content in unbranded mode. |
allowExternalLinks |
true |
Allow URL hotspots to open new pages. |
nadir |
none | Circular nadir image configuration. |
snapshotEndpoint |
none | Default high-resolution snapshot endpoint. |
colorFilters |
neutral | WebGL color adjustments. |
npm run buildnpm run lintnpm run typechecknpm run test:unit