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

@vuvandinh203/krpano-iframe-toolkit

v1.0.1

Published

React client toolkit for controlling a krpano tour inside an iframe.

Readme

@vuvandinh203/krpano-iframe-toolkit

React toolkit for controlling a krpano tour running inside an iframe.

The package includes:

  • dist/krpano-iframe-toolkit.js: a browser bridge script loaded by the krpano tour HTML.
  • React APIs: KrpanoTour, useKrpanoTour, and createKrpanoIframeClient.
  • TypeScript hints for common krpano variables such as view.fov, xml.scene, hotspot[name].visible, and more.

Installation

npm install @vuvandinh203/krpano-iframe-toolkit

React must be installed by the host app:

npm install react react-dom

How It Works

Your React app renders a tour page inside an iframe. The tour page loads krpano-iframe-toolkit.js, which exposes window.KrpanoIframeBridge. React and krpano then communicate through postMessage.

React app
  -> iframe
    -> tour.html
      -> krpano
      -> KrpanoIframeBridge

1. Add The Bridge To The krpano Tour

Copy the bridge file from the installed package into the public/static directory used by the tour HTML:

cp node_modules/@vuvandinh203/krpano-iframe-toolkit/dist/krpano-iframe-toolkit.js public/krpano-iframe-toolkit.js

Then load it in tour.html and initialize it inside embedpano.onready:

<div id="pano" style="width: 100%; height: 100%;"></div>

<script src="./tour.js"></script>
<script src="./krpano-iframe-toolkit.js"></script>
<script>
  embedpano({
    xml: "./tour.xml",
    target: "pano",
    html5: "only",
    onready: function (krpano) {
      KrpanoIframeBridge.init(krpano, {
        allowedOrigins: [
          "http://localhost:5173",
          "https://app.example.com"
        ],
        allowRawCall: false,
        autoEmitCommonEvents: true
      });
    }
  });
</script>

You can also load the bridge from a CDN after publishing the package:

<script src="https://unpkg.com/@vuvandinh203/krpano-iframe-toolkit/dist/krpano-iframe-toolkit.js"></script>

or:

<script src="https://cdn.jsdelivr.net/npm/@vuvandinh203/krpano-iframe-toolkit/dist/krpano-iframe-toolkit.js"></script>

Bridge Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | allowedOrigins | string[] | [] | React app origins allowed to control the tour. Must match protocol, host, and port. | | debug | boolean | false | Enables bridge logs in the iframe console. | | allowRawCall | boolean | true | Allows krpano.call(...) through tour.runAction(...). Disable it in production unless needed. | | autoEmitCommonEvents | boolean | false | Enables built-in hotspotClick, sceneChange, and viewChange events. | | autoEmitHotspotClicks | boolean | false | Automatically emits hotspot click events. | | autoEmitSceneChanges | boolean | false | Automatically emits scene change events. | | autoEmitViewChanges | boolean | false | Automatically emits view change events. | | hotspotClickEventName | string | "hotspotClick" | Custom event name for auto hotspot clicks. | | sceneChangeIntervalMs | number | 500 | Polling interval for scene changes. | | viewChangeIntervalMs | number | 250 | Polling interval for view changes. | | viewChangePrecision | number | 2 | Decimal precision used when comparing view changes. |

2. Quick Start In React

Use KrpanoTour when you want the simplest React integration. Pass the tour URL to src, then receive the connected client in onTourReady.

import { useState } from "react";
import {
  KrpanoTour,
  type KrpanoIframeClient
} from "@vuvandinh203/krpano-iframe-toolkit";

export default function TourPage() {
  const [tour, setTour] = useState<KrpanoIframeClient | null>(null);

  async function loadLobby() {
    await tour?.loadScene("scene_lobby", {
      blend: 1,
      flags: "MERGE"
    });
  }

  async function lookAtReception() {
    await tour?.lookAt({
      hlookat: 120,
      vlookat: 0,
      fov: 80,
      duration: 1
    });
  }

  return (
    <div style={{ height: 640 }}>
      <KrpanoTour
        title="360 Tour"
        src="https://tour.example.com/tour.html"
        onTourReady={setTour}
      />

      <button disabled={!tour} onClick={loadLobby}>
        Load lobby
      </button>
      <button disabled={!tour} onClick={lookAtReception}>
        Look at reception
      </button>
    </div>
  );
}

If src is an absolute URL, iframeOrigin is detected automatically. Provide iframeOrigin only for relative URLs or when you need to override it:

<KrpanoTour
  src="/tour/tour.html"
  iframeOrigin="https://tour.example.com"
  onTourReady={(tour) => {
    tour.loadScene("scene_lobby");
  }}
/>

3. Use A Ref Instead Of State

import { useRef } from "react";
import {
  KrpanoTour,
  type KrpanoTourHandle
} from "@vuvandinh203/krpano-iframe-toolkit";

export default function TourPage() {
  const tourRef = useRef<KrpanoTourHandle>(null);

  async function zoomIn() {
    await tourRef.current?.client?.zoomIn({
      step: 10,
      minFov: 30
    });
  }

  return (
    <>
      <KrpanoTour
        ref={tourRef}
        title="360 Tour"
        src="https://tour.example.com/tour.html"
        style={{ height: 640 }}
      />

      <button onClick={zoomIn}>Zoom in</button>
    </>
  );
}

4. Built-In Tour Events

For common tour events, enable autoEmitCommonEvents once in tour.html:

KrpanoIframeBridge.init(krpano, {
  allowedOrigins: ["http://localhost:5173"],
  autoEmitCommonEvents: true
});

Then listen in React without editing each XML hotspot and without calling subscribeEvent:

<KrpanoTour
  src="https://tour.example.com/tour.html"
  onTourReady={(tour) => {
    tour.onHotspotClick((payload) => {
      console.log("Hotspot:", payload.name);
      console.log("Scene:", payload.scene);
      console.log("Position:", payload.ath, payload.atv);
    });

    tour.onSceneChange((payload) => {
      console.log("Scene changed:", payload.scene);
    });

    tour.onViewChange((payload) => {
      console.log("View:", payload.hlookat, payload.vlookat, payload.fov);
    });
  }}
/>

To enable only selected built-in events:

KrpanoIframeBridge.init(krpano, {
  allowedOrigins: ["http://localhost:5173"],
  autoEmitHotspotClicks: true,
  autoEmitSceneChanges: true,
  autoEmitViewChanges: false
});

If hotspots are created dynamically after the tour is ready, bind them again:

await tour.enableHotspotClickEvents();

5. Custom Events

Use custom events for application-specific behavior, such as opening a product modal, tracking analytics, or opening a contact form.

In the tour HTML or in a JS function called by krpano XML:

KrpanoIframeBridge.emitAlways("openProduct", {
  id: "product_123",
  title: "Sofa",
  scene: krpano.get("xml.scene")
});

In React:

tour.onEvent("openProduct", (payload) => {
  console.log("Open product:", payload);
});

If you want the event to be sent only when React explicitly subscribes, use emit with subscribeEvent:

KrpanoIframeBridge.emit("openProduct", {
  id: "product_123"
});
tour.onEvent("openProduct", (payload) => {
  console.log(payload);
});

tour.subscribeEvent("openProduct");

6. TypeScript Hints For krpano Variables

getValue and setValue provide autocomplete for many common krpano paths:

const fov = await tour.getValue("view.fov");
const scene = await tour.getValue("xml.scene");
const isMobile = await tour.getValue("device.mobile");

await tour.setValue("view.fov", 90);
await tour.setValue("view.hlookat", 120);
await tour.setValue("hotspot[spot_1].visible", true);
await tour.setValue("layer[menu].alpha", 0.8);

Known paths get basic return types:

| Path | Type | | --- | --- | | view.fov | number | | xml.scene | string | | device.mobile | boolean | "true" | "false" | | hotspot[name].visible | boolean | "true" | "false" |

Custom tour variables are still allowed:

const value = await tour.getValue("my_custom_plugin.some_value");
await tour.setValue("my_custom_plugin.some_value", "hello");

7. Hook API

Use useKrpanoTour when you want to render the iframe yourself.

import { useEffect, useState } from "react";
import {
  useKrpanoTour,
  type KrpanoView
} from "@vuvandinh203/krpano-iframe-toolkit";

export default function CustomTourPage() {
  const [view, setView] = useState<KrpanoView | null>(null);

  const {
    iframeRef,
    client: tour,
    isReady,
    isConnected,
    lastError
  } = useKrpanoTour({
    tourUrl: "https://tour.example.com/tour.html",
    requestTimeoutMs: 10000,
    autoConnect: true,
    onTourReady: (client) => {
      client.getView().then(setView);
    }
  });

  async function zoomIn() {
    await tour?.zoomIn({ step: 10, minFov: 30 });

    if (tour) {
      setView(await tour.getView());
    }
  }

  return (
    <section>
      <iframe
        ref={iframeRef}
        title="360 Tour"
        src="https://tour.example.com/tour.html"
        style={{ width: "100%", height: 640, border: 0 }}
      />

      <button disabled={!isReady || !isConnected} onClick={zoomIn}>
        Zoom in
      </button>

      {view && <pre>{JSON.stringify(view, null, 2)}</pre>}
      {lastError && <p>{lastError.message}</p>}
    </section>
  );
}

For lower-level iframe control, use useKrpanoIframe and pass iframeOrigin manually.

const { iframeRef, client } = useKrpanoIframe({
  iframeOrigin: "https://tour.example.com",
  requestTimeoutMs: 10000,
  autoConnect: true
});

8. Plain Client API

Use createKrpanoIframeClient when you are not using the React component or hook.

import { createKrpanoIframeClient } from "@vuvandinh203/krpano-iframe-toolkit";

const iframe = document.querySelector<HTMLIFrameElement>("#tour");

if (iframe) {
  const tour = createKrpanoIframeClient(iframe, {
    iframeOrigin: "https://tour.example.com",
    requestTimeoutMs: 10000
  });

  await tour.connect();
  await tour.loadScene("scene_lobby", { blend: 1 });
  await tour.lookAt({ hlookat: 90, vlookat: 0, fov: 75, duration: 1 });
}

9. Client Methods

| Method | Description | | --- | --- | | connect() | Connects to the bridge. | | ping() | Checks whether the bridge is responding. | | getValue(name) | Reads a krpano variable using krpano.get(name). | | setValue(name, value) | Writes a krpano variable using krpano.set(name, value). | | runAction(action) | Runs a raw krpano action using krpano.call(action). | | loadScene(scene, options) | Loads a scene. | | getCurrentScene() | Returns the current scene name. | | getView() | Returns { hlookat, vlookat, fov, scene }. | | setView(options) | Tweens the current view. | | lookAt(options) | Moves the view with lookto. | | zoomIn(options) | Zooms in by reducing view.fov. | | zoomOut(options) | Zooms out by increasing view.fov. | | enableHotspotClickEvents() | Rebinds auto hotspot click events. Useful for dynamic hotspots. | | onHotspotClick(handler) | Listens for built-in hotspot click events. | | onSceneChange(handler) | Listens for built-in scene change events. | | onViewChange(handler) | Listens for built-in view change events. | | onEvent(eventName, handler) | Listens for a custom event. | | subscribeEvent(eventName, options) | Enables a custom event when the tour uses emit. | | unsubscribeEvent(eventName) | Disables a custom event subscription. | | listSubscribedEvents() | Lists active event subscriptions in the bridge. | | onReady(handler) | Listens for the bridge ready message. | | destroy() | Removes listeners and rejects pending requests. |

10. Common Commands

await tour.loadScene("scene_lobby", {
  blend: 1,
  flags: "MERGE"
});
await tour.lookAt({
  hlookat: 120,
  vlookat: -5,
  fov: 80,
  duration: 1
});
await tour.setView({
  hlookat: 0,
  vlookat: 0,
  fov: 90,
  duration: 0.8
});
await tour.zoomIn({
  step: 10,
  minFov: 30
});
await tour.zoomOut({
  step: 10,
  maxFov: 150
});

Use runAction carefully. It requires allowRawCall !== false in the tour bridge:

await tour.runAction("lookto(0, 0, 90);");

Troubleshooting

The React app cannot control the iframe

Check allowedOrigins in tour.html. The value must exactly match the React app origin, including protocol and port.

allowedOrigins: ["http://localhost:5173"]

Commands time out

Common causes:

  • the iframe URL is wrong,
  • the bridge script is not loaded in tour.html,
  • KrpanoIframeBridge.init(...) is not called inside embedpano.onready,
  • allowedOrigins does not include the React app origin,
  • iframeOrigin is wrong when using a relative URL.

runAction fails

If the bridge is initialized with:

allowRawCall: false

then tour.runAction(...) is blocked. Prefer safer methods such as loadScene, lookAt, setView, zoomIn, and zoomOut.

Publishing

Before publishing:

npm run typecheck
npm run build
npm pack --dry-run

Publish this scoped package as public:

npm publish --access public