npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ansonphong/360-viewer

v6.0.4

Published

Ultra-lightweight, headless 360 panorama renderer — Three.js, no DOM beyond canvas, 30KB core

Readme

@ansonphong/360-viewer

Ultra-lightweight, headless 360° panorama renderer.

Canvas-only Three.js viewer with no DOM beyond <canvas> — wraps the core renderer, multi-image switching, a typed event emitter, theme bridge, and library loader into a single public class.

npm version npm downloads License: MIT Three.js r128 Bundle size

Live demo → · Full docs → · Companion UI package →


Why this viewer

  • 30KB core, no build step required — drop in a <script> tag and you have mouse + touch + keyboard panning, pinch-zoom, projection switching, fullscreen, auto-rotation.
  • Headless on purpose — no sidebar, no toolbar, no opinion on layout. Pair with @ansonphong/360-viewer-library for batteries-included chrome, or wire it into your own UI.
  • Adaptive resolution loading — pick from 8K / 4K / 2K variants per image, swap at runtime, with smooth fade-through-black transitions.
  • One Three.js instance — peer-deps cleanly with whatever Three the host page already loads.
  • Latest-wins concurrency — calling loadImage() mid-transition cancels the in-flight load instead of locking up.

Install

npm install @ansonphong/360-viewer three

Three.js r128 is a peer dependency. The viewer expects it on window.THREE (UMD) or as an ESM import (ESM bundle).


Quick start

Option A — Plain HTML + UMD (no build step)

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ansonphong/360-viewer@6/dist/viewer.css" />
<script src="https://unpkg.com/@ansonphong/360-viewer@6/dist/viewer.umd.js"></script>

<div id="viewer" style="width: 100%; height: 600px;"></div>
<script>
  const viewer = new Phong360Viewer({
    container: 'viewer',
    autoRotate: true,
    autoRotationRate: 1.5,
  });
  viewer.loadImage('/panoramas/sunset.jpg');
</script>

Option B — ESM (Vite / webpack / Rollup / Next.js)

import 'three';                                    // once into window.THREE
import { Phong360Viewer } from '@ansonphong/360-viewer';
import '@ansonphong/360-viewer/dist/viewer.css';

const viewer = new Phong360Viewer({
  container: document.getElementById('viewer'),
  autoRotate: true,
});
await viewer.loadImage('/panoramas/sunset.jpg');

Option C — Standalone UMD (no peer-dep coordination)

If you want one self-contained file that bundles its own Three.js reference resolution:

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://unpkg.com/@ansonphong/360-viewer@6/dist/viewer.standalone.umd.js"></script>

Same window.Phong360Viewer global. Internally uses a shim that resolves three to window.THREE at parse time — guarantees a single Three.js instance even on pages that already load Three.


Public API

Constructor

new Phong360Viewer({
  container,             // element or element id (required)
  libraryUrl,            // optional: library.json manifest for multi-image
  projection: 'equirect',// 'equirect' | 'little-planet'
  resolution: 'auto',    // 'auto' | '2k' | '4k' | '8k'
  theme: 'dark',         // 'dark' | 'light' | 'auto'
  accent: '#e13e13',     // CSS color for UI accents
  autoRotate: false,
  autoRotationRate: 1.0,
  fov: 75,
  controls: {
    enableZoom: true,    // false → mouse wheel scrolls the host page
    enablePan: true,     // false → drag/keyboard pan disabled, pinch still zooms
  },
  loading: {
    backgroundColor: '#000',
    fadeInDuration: 500, // ms before swap
    fadeOutDuration: 500,// ms after paint
    showSpinner: true,
  },
  transition: { /* ... */ },
  performance: {
    maxPixelRatio: 2,    // DPR clamp for high-DPI GPUs
    maxAnisotropy: 8,    // texture filtering ceiling
    renderMode: 'on-demand', // 'continuous' | 'on-demand'
  },
})

Methods

// Image loading
await viewer.loadImage(url)            // latest-wins
viewer.selectImage(id)                 // jump to image in library by id
viewer.nextImage()
viewer.prevImage()
viewer.getCurrentImage()

// Library
viewer.setLibrary(manifest)            // pass a library.json object
viewer.setSections(sections)
viewer.getSections()
viewer.getImages()
viewer.getLibraryData()

// Runtime controls
viewer.setAutoRotate(true)
viewer.getAutoRotate()
viewer.setProjection('little-planet')
viewer.setResolution('4k')
viewer.getResolution()
viewer.setTheme('light')
viewer.setAccent('#6366f1')
viewer.setPerformance({ maxPixelRatio: 1 })

// Render lifecycle
viewer.pause()
viewer.resume()
viewer.isPaused()

// Three.js access (advanced)
viewer.getContext()                    // { scene, camera, renderer, controls }

// Cleanup
viewer.destroy()

Events

Subscribe via viewer.on(name, handler) / viewer.off(name, handler):

| Event | Payload | When | |---|---|---| | image:loading | { url } | New image fetch started | | image:loaded | { url, image } | GPU upload + first paint complete | | image:visible | { url, image } | Fade-out finished — fully visible to user | | image:error | { url, error } | Load failed | | library:loaded | { manifest } | library.json parsed | | projection:change | { projection } | After setProjection() | | resolution:change | { resolution } | After setResolution() or auto-pick | | theme:change | { theme } | After setTheme() |

The container element also dispatches bubbling CustomEvents for outside listeners that don't have a viewer reference:

  • phong-viewer-loadeddetail: { url }, fires after fade-out (image fully visible)
  • phong-viewer-paused / phong-viewer-resumed
  • phong-viewer-errordetail: { url, error }

Theming

All UI accents are driven by CSS custom properties on the container scope:

.my-viewer {
  --pv-accent: #e13e13;
  --pv-bg: #000;
  --pv-text: #fff;
  --pv-surface: rgba(20, 20, 20, 0.85);
}

Or switch built-in themes at runtime: viewer.setTheme('dark' | 'light' | 'auto').

Engine CSS (viewer.css) is canvas-relevant only — overlay, transitions, theme variables. All sidebar/toolbar/info-bar styles belong to @ansonphong/360-viewer-library.


Bundles in dist/

| File | Purpose | Three.js | |---|---|---| | viewer.esm.js | ESM, modern bundlers | peer dep (external three) | | viewer.umd.js | UMD, plain HTML | reads window.THREE via shim | | viewer.standalone.umd.js | Self-contained UMD | reads window.THREE via shim | | viewer.css | Canvas-relevant styles | — |

All bundles expose the global Phong360Viewer (UMD) or named export Phong360Viewer (ESM).


Multi-viewer pages

Each viewer instance creates its own loading overlay scoped to its container — no shared global state. To pre-place an overlay (e.g. to prevent FOUC), drop a <div id="loading-overlay"> inside the viewer's container element and the engine will adopt it.

loadImage() is latest-wins. A new call cancels the previous in-flight load; the prior Promise never resolves. Consumers tracking a specific call should await its Promise.


Performance controls

new Phong360Viewer({
  container: 'viewer',
  performance: {
    maxPixelRatio: 1.5,       // cap DPR on high-density mobile to cut fill cost
    maxAnisotropy: 4,         // lower texture filtering to free VRAM
    renderMode: 'on-demand',  // pause render loop when nothing changed
  },
});

The engine also pauses rendering automatically when the canvas is off-screen (IntersectionObserver) and clamps post-resume delta to 100ms so long pauses don't produce camera jumps.


Migration from 5.x → 6.x

Pure rename, no behavior changes:

| 5.x | 6.x | |---|---| | @ansonphong/360-engine | @ansonphong/360-viewer | | Phong360Engine class | Phong360Viewer class | | dist/engine.* | dist/viewer.* | | --p360-* / .p360-* CSS | --pv-* / .pv-* | | phong-360-loaded event | phong-viewer-loaded | | p360-help event | pv-help |

The inner class Phong360ViewerCore is unchanged — still the core Three.js renderer.

npm uninstall @ansonphong/360-engine
npm install @ansonphong/360-viewer
- import { Phong360Engine } from '@ansonphong/360-engine';
+ import { Phong360Viewer } from '@ansonphong/360-viewer';
# Sweep CSS
sed -i 's/--p360-/--pv-/g; s/\.p360-/.pv-/g' your-styles.css

The old package remains installable indefinitely with a deprecation notice — existing lockfiles do not break.


Pair with the library UI

Need a full gallery experience — sidebar with sections, toolbar with controls, glassmorphic info bar with prev/next, deep-linking, theme toggle? Install the companion package:

npm install @ansonphong/360-viewer @ansonphong/360-viewer-library three

See @ansonphong/360-viewer-library.


Browser support

Modern evergreen browsers (Chrome, Firefox, Safari, Edge). Requires WebGL 1.0 and ES2017+. No IE support.


Links


License

MIT — see LICENSE.