Skip to main content
The Virtual Tour plugin connects multiple panoramas into a navigable tour. Users move between nodes by clicking arrow links. Nodes can be positioned manually or automatically from GPS coordinates, and data can be supplied all at once or loaded on demand from a server.

Installation

npm install @photo-sphere-viewer/virtual-tour-plugin
This plugin requires its stylesheet. Import @photo-sphere-viewer/virtual-tour-plugin/index.css or add the CDN link to your <head>.

Usage

import { Viewer } from '@photo-sphere-viewer/core';
import { VirtualTourPlugin } from '@photo-sphere-viewer/virtual-tour-plugin';

const viewer = new Viewer({
    plugins: [
        VirtualTourPlugin.withConfig({
            nodes: [...],
            // or for server mode:
            // getNode: async (id) => { ... },
            // startNodeId: 'node-1',
        }),
    ],
});

Data mode

Provide all nodes upfront in the nodes array. You can replace them later with setNodes().
VirtualTourPlugin.withConfig({
    nodes: [
        {
            id: 'node-1',
            panorama: '001.jpg',
            links: [{ nodeId: 'node-2', position: { textureX: 1500, textureY: 780 } }],
        },
        {
            id: 'node-2',
            panorama: '002.jpg',
            links: [{ nodeId: 'node-1', position: { textureX: 3000, textureY: 780 } }],
        },
    ],
})

Position mode

Each link specifies its position on the panorama using yaw/pitch or textureX/textureY coordinates.
const node = {
    id: 'node-1',
    panorama: '001.jpg',
    links: [
        {
            nodeId: 'node-2',
            position: { textureX: 1500, textureY: 780 },
        },
    ],
};
The Gallery, Map, Plan, and Compass plugins integrate seamlessly with the virtual tour.

Node definition

id
string
required
Unique identifier for the node.
panorama
required
The panorama to display. Accepts the same value as the main viewer panorama option.
Links to other nodes from this node. Required in client mode. See Links below.
gps
number[]
GPS coordinates of the node: [longitude, latitude, altitude?]. Required when using positionMode: 'gps'.
name
string
Short name shown in link tooltips and the Gallery plugin.
caption
string
Caption displayed in the viewer header. Same as the main viewer caption option.
description
string
Description text. Same as the main viewer description option.
thumbnail
string
Thumbnail URL used in the Gallery plugin.
Whether to show this node in the Gallery plugin.
markers
MarkerConfig[]
Additional markers to display on this node. Requires the Markers plugin. Markers can use standard position (yaw/pitch) or, in GPS mode, a gps coordinate.
map
Hotspot configuration for the Map plugin (client mode only). Set to false to hide this node from the map.
plan
Hotspot configuration for the Plan plugin (client + GPS mode only). Set to false to hide this node from the plan.
panoData
object
Panorama data passed to the viewer. Same as the main viewer panoData option.
sphereCorrection
object
Sphere correction applied to the panorama. Same as the main viewer sphereCorrection option.
data
any
Arbitrary data attached to the node.
nodeId
string
required
ID of the target node.
position
{ yaw, pitch } | { textureX, textureY }
Position of the link on the panorama in spherical or texture coordinates. Required in manual mode.
gps
number[]
GPS coordinates of the target node. Required in GPS + server mode so the link can be positioned without loading the target node first.
Positional offset applied to the arrow. Adjusting this moves the displayed arrow without changing where the viewer rotates when the link is clicked. depth is used in 3D render mode to manage overlapping arrows.
arrowStyle
object
Overrides the global arrowStyle for this specific link.
data
any
Arbitrary data attached to the link.

Plugin configuration

dataMode
'client' | 'server'
default:"'client'"
How node data is provided. Not updatable.
positionMode
'manual' | 'gps'
default:"'manual'"
How link positions are determined. Not updatable.
renderMode
'2d' | '3d'
default:"'3d'"
How navigation arrows are rendered. Not updatable.
nodes
array
Initial node list (client mode only). Not updatable — use setNodes() instead.
getNode
function
(nodeId: string) => Promise<Node>. Callback to fetch a node’s configuration (server mode only). Required in server mode.
startNodeId
string
ID of the initially loaded node. If omitted, the first node is used. Required in server mode.
preload
boolean | function
default:"false"
Preload linked nodes before the user navigates to them. Can be a function (node, link) => boolean for selective preloading.
transitionOptions
object | function
Configures the transition when switching nodes. Can be a static object or a callback (toNode, fromNode?, fromLink?) => options.Static object fields:
FieldTypeDefaultDescription
showLoaderbooleantrueShow the loader while loading the next panorama
effect'none' | 'fade' | 'black' | 'white''fade'Transition effect
speedstring | number'20rpm'Speed or duration of the transition
rotationbooleantrueRotate toward the next node before transitioning
When using a callback, two additional fields are available: rotateTo (where to rotate before switching) and zoomTo (new zoom level).
Show link directions on the Compass plugin overlay.
Show a tooltip on each link arrow containing the node’s name, thumbnail, and caption.
(content: string, link, node) => string. Callback to customize the tooltip content for a link. The first argument is the default tooltip HTML.
arrowStyle
object
Global style for link arrows.
arrowStyle: {
    // use an image instead of the default circular button
    image: 'path/to/arrow.png',
    size: { width: 80, height: 80 },
    // or add CSS
    className: 'my-arrow',
    style: { opacity: 0.8 },
}
Default is a circular button with a ripple effect, 80×80px.
arrowsPosition
object
Fine-tuning for arrow placement (3D mode). All values are in radians unless noted.
arrowsPosition: {
    minPitch: 0.3,                    // minimum vertical angle
    maxPitch: Math.PI / 2,            // maximum vertical angle
    linkOverlapAngle: Math.PI / 4,    // angle below which arrows become transparent
    linkPitchOffset: -0.1,            // vertical offset (2D + GPS mode)
}
map
object
Configuration for Map plugin integration (client mode only). The imageUrl for the map must be set here, not in the Map plugin configuration.
// Manual positioning
map: {
    imageUrl: 'map.jpg',
}

// GPS-based automatic positioning
map: {
    imageUrl: 'map.jpg',
    size: { width: 1600, height: 1200 },
    extent: [-80.158123, 25.668050, -80.153824, 25.665308],
}

Methods

setNodes(nodes, startNodeId?) (client mode only)

Replaces all nodes and navigates to the first one (or to startNodeId).

updateNode(node) (client mode only)

Updates a single node. Only id is required; all other fields are optional. If the updated node is the current one, the viewer refreshes accordingly.
virtualTourPlugin.updateNode({
    id: 'node-1',
    caption: 'New caption',
    links: [...newLinks],
});

setCurrentNode(nodeId, options?)

Navigates to a specific node. Pass options to override transitionOptions for this transition only. Rotates the view to face a specific link. Default speed is 8rpm.
virtualTourPlugin.gotoLink('node-2', '4rpm')
    .then(() => { /* animation complete */ });

getCurrentNode()

Returns the current node object.

getLinkPosition(nodeId): Position

Returns the current viewer position (yaw/pitch) of a link.

Events

node-changed(node, data)

Fired when the active node changes.
virtualTourPlugin.addEventListener('node-changed', ({ node, data }) => {
    console.log(`Now viewing: ${node.id}`);
    if (data.fromNode) {
        console.log(`Previously: ${data.fromNode.id}`);
    }
});
Fired when the cursor enters or leaves a navigation arrow.

Build docs developers (and LLMs) love