@ansonphong/360-viewer-library
v6.0.1
Published
Batteries-included sidebar / toolbar / info-bar chrome for 360 panorama experiences — drop-in UI for @ansonphong/360-viewer
Maintainers
Readme
@ansonphong/360-viewer-library
Batteries-included sidebar / toolbar / info-bar chrome for 360° panorama experiences.
Drop-in UI layer that sits on top of @ansonphong/360-viewer — adds section-based browsing, toolbar with controls, glassmorphic info bar, help modal, deep-linking, theme toggle, and a slot/decorator extension system. No framework dependencies.
Live demo → · Full docs → · Engine package →
What this gives you
- Section-based sidebar with 9 built-in templates (
grid,feed,accordion,hero,list,carousel,avatar-row,avatar-grid,empty) - Toolbar with projection toggle, resolution switcher, theme toggle, fullscreen, info, help
- Glassmorphic info bar with prev/next navigation, image title/caption
- Help modal triggered by the
pv-helpkeyboard event and toolbar button - Deep-linking —
?img=<slug>reads/writes by default, fully configurable - Theme system — light / dark / auto with CSS custom properties + accent-color
- Decorator API — extend thumbnails, section headings, sidebar, info bar, toolbar without forking
- Slot system — replace named UI regions with your own factory functions
- JSON-driven config — one
360-viewer.jsoncontrols title, theme, accent, panel width, favicon, social links
Use the engine alone if you're embedding a panorama into a marketing hero or wiring up your own chrome. Use this package when you want a full gallery experience out of the box.
Install
npm install @ansonphong/360-viewer @ansonphong/360-viewer-library threeThe engine and Three.js r128 are peer-resolved dependencies. The library-ui pins the engine version to ensure CSS prefixes and event names stay in sync.
Quick start
Plain HTML + UMD (no build step)
<!-- Peer deps -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@phosphor-icons/[email protected]/src/regular/style.css" />
<!-- Engine + Library UI -->
<link rel="stylesheet" href="https://unpkg.com/@ansonphong/360-viewer@6/dist/viewer.css" />
<link rel="stylesheet" href="https://unpkg.com/@ansonphong/360-viewer-library@6/dist/viewer-library.css" />
<script src="https://unpkg.com/@ansonphong/360-viewer@6/dist/viewer.umd.js"></script>
<script src="https://unpkg.com/@ansonphong/360-viewer-library@6/dist/viewer-library.umd.js"></script>
<div id="viewer" style="width: 100%; height: 100vh;"></div>
<script>
const gallery = new Phong360ViewerLibrary({
containerId: 'viewer',
libraryUrl: 'library/library.json',
configUrl: '360-viewer.json',
baseUrl: 'library/',
theme: 'auto',
});
</script>ESM (Vite / webpack / Rollup / Next.js)
import 'three';
import { Phong360ViewerLibrary } from '@ansonphong/360-viewer-library';
import '@ansonphong/360-viewer/dist/viewer.css';
import '@ansonphong/360-viewer-library/dist/viewer-library.css';
const gallery = new Phong360ViewerLibrary({
containerId: 'viewer',
libraryUrl: '/library.json',
configUrl: '/360-viewer.json',
theme: 'auto',
});Phosphor Icons is required for the toolbar / sidebar / info-bar glyphs. Without it you'll see empty boxes where icons should be — there is no built-in fallback.
Configuration via 360-viewer.json
Everything visible — title, theme, accent, sections, layout — is driven by a single JSON file:
{
"context": {
"type": "profile",
"title": "Your Name",
"subtitle": "360 Photography",
"avatar": "assets/avatar.jpg",
"theme": "dark",
"accent": "#6366f1",
"panelWidth": 420,
"infoBar": "center",
"favicon": "🎨",
"links": [
{ "url": "https://yoursite.com", "label": "Website" },
{ "url": "https://instagram.com/you", "label": "Instagram" }
]
},
"sections": {
"Landscapes": { "title": "Landscapes", "icon": "mountains", "template": "grid" },
"Cities": { "title": "Cities", "icon": "city", "template": "feed" }
}
}library.json (v4.0) carries the actual image manifest — the library-ui reads it through the engine. See LIBRARY-FORMAT.md.
Public API
Constructor
new Phong360ViewerLibrary({
containerId, // string id of the container element (required)
libraryUrl, // path to library.json (v4.0 manifest)
configUrl, // path to 360-viewer.json
baseUrl, // base path prepended to relative image URLs
theme: 'auto', // 'dark' | 'light' | 'auto'
accent: '#e13e13',
urlSync: true, // true | false | { read, write } for custom deep-linking
panelWidth: 420,
infoBar: 'center', // 'center' | 'left' | 'right' | 'off'
// ...all engine config also forwarded
})Decorator API
Extend the UI without forking:
// Add a custom decorator to every thumbnail (e.g. owner badge, view count)
gallery.addThumbnailDecorator(({ image, el }) => {
if (image.featured) el.classList.add('is-featured');
});
// Decorate section headings
gallery.addSectionHeadingDecorator(({ section, el }) => { /* ... */ });
// Inject a custom sidebar section
gallery.addSidebarSection({
id: 'about',
title: 'About',
template: 'hero',
render: (root) => { /* paint into root */ },
});
// Mount custom content into the info bar
gallery.setInfoBarSlot('left', document.querySelector('#left-extras'));
gallery.setInfoBarSlot('right', document.querySelector('#right-extras'));
// Add a toolbar button
gallery.addToolbarButton({
id: 'share',
icon: 'share-network',
title: 'Share',
onClick: () => navigator.share?.({ url: location.href }),
});Memory bounds: decorators are capped — warning at 20 per kind, hard cap at 100. This protects against accidental N² registration in re-render loops.
Slot system
Swap out entire UI regions with your own factory functions:
import { SLOT_NAMES } from '@ansonphong/360-viewer-library';
gallery.setSlot(SLOT_NAMES.SIDEBAR, ({ container, gallery }) => {
// return cleanup fn, or undefined
const el = document.createElement('aside');
el.innerHTML = '<nav>...</nav>';
container.appendChild(el);
return () => container.removeChild(el);
});
gallery.clearSlot(SLOT_NAMES.SIDEBAR);Available slot names: SIDEBAR, TOOLBAR, INFO_BAR, HELP, plus per-template slots.
Methods
gallery.getViewer() // underlying Phong360Viewer instance
gallery.selectImage(id)
gallery.openSidebar() / closeSidebar() / toggleSidebar()
gallery.openHelp() / closeHelp()
gallery.setTheme('light')
gallery.setAccent('#6366f1')
gallery.refresh() // re-fetch library.json + 360-viewer.json
gallery.destroy()Events
The library-ui re-emits engine events and adds its own:
| Event | Source | Payload |
|---|---|---|
| image:visible | engine | { url, image } |
| image:loaded | engine | { url, image } |
| section:click | library-ui | { section } |
| sidebar:open / sidebar:close | library-ui | — |
| help:open / help:close | library-ui | — |
| theme:change | engine | { theme } |
Plus the bubbling CustomEvent on the container: pv-help toggles the help modal.
Templates
The sidebar renders sections via a pluggable template engine. Pick a template per section in your 360-viewer.json:
| Template | Description | Use case |
|---|---|---|
| grid | Responsive thumbnail grid | Default browsing |
| feed | Vertical list with large thumbnails | Recent / featured |
| accordion | Collapsible group containing an inner template | Category organization |
| hero | Single large featured image | Spotlight |
| list | Compact rows with small thumbnails | Search results, dense lists |
| carousel | Horizontal scrolling strip | Trending, related |
| avatar-row | Horizontal circular avatars | Creator highlights |
| avatar-grid | Grid of avatar cards | Creator directory |
| empty | Placeholder | No-content state |
Custom templates can be registered — see TEMPLATES.md.
Theming
All visuals key off CSS custom properties under the --pv-* namespace (shared with the engine):
.my-gallery {
--pv-accent: #e13e13;
--pv-bg: #0a0a0a;
--pv-surface: rgba(20, 20, 20, 0.85);
--pv-text: #fff;
--pv-text-muted: rgba(255, 255, 255, 0.6);
--pv-border: rgba(255, 255, 255, 0.1);
--pv-panel-width: 420px;
}Or swap built-in themes at runtime: gallery.setTheme('dark' | 'light' | 'auto').
See THEMING.md for the full custom-property reference.
Bundles in dist/
| File | Purpose | Engine |
|---|---|---|
| viewer-library.esm.js | ESM | peer dep |
| viewer-library.umd.js | UMD | reads window.Phong360Viewer |
| viewer-library.standalone.umd.js | Self-contained UMD | reads window.Phong360Viewer |
| viewer-library.css | Sidebar / toolbar / info-bar styles | — |
All bundles expose Phong360ViewerLibrary as a global (UMD) or named export (ESM).
Migration from 5.x → 6.x
Pure rename, no behavior changes:
| 5.x | 6.x |
|---|---|
| @ansonphong/360-library-ui | @ansonphong/360-viewer-library |
| Phong360LibraryUI class | Phong360ViewerLibrary class |
| dist/library-ui.* | dist/viewer-library.* |
| --p360-* / .p360-* CSS | --pv-* / .pv-* |
| p360-help event | pv-help |
| peer engine @ansonphong/[email protected] | @ansonphong/360-viewer@^6.0.0 |
npm uninstall @ansonphong/360-library-ui @ansonphong/360-engine
npm install @ansonphong/360-viewer-library @ansonphong/360-viewer- import { Phong360LibraryUI } from '@ansonphong/360-library-ui';
+ import { Phong360ViewerLibrary } from '@ansonphong/360-viewer-library';sed -i 's/--p360-/--pv-/g; s/\.p360-/.pv-/g' your-styles.cssThe old package remains installable indefinitely with a deprecation notice — existing lockfiles do not break.
Browser support
Modern evergreen browsers (Chrome, Firefox, Safari, Edge). Requires WebGL 1.0 and ES2017+.
Links
- GitHub: ansonphong/360-VIEWER — full docs, examples, gallery template, deploy scripts
- Live demo: 360.phong.com
- Engine package:
@ansonphong/360-viewer - Fork Guide (build your own gallery): docs/FORK-GUIDE.md
- Issues: GitHub issues
- Author: Phong
License
MIT — see LICENSE.
