Events
The StorySplat Viewer emits events that you can listen to for building interactive experiences.
Subscribing to Events
// Subscribe
viewer.on('ready', () => {
console.log('Viewer is ready');
});
// Unsubscribe
const callback = () => console.log('Frame changed');
viewer.on('frameChange', callback);
viewer.off('frameChange', callback);
Available Events
ready
Fired when the viewer is fully initialized and ready.
viewer.on('ready', () => {
console.log('Viewer ready!');
viewer.play(); // Safe to call methods now
});
loaded
Fired when the splat or frame sequence has finished loading.
progress
Fired during loading with progress updates.
viewer.on('progress', ({ progress, text }) => {
console.log(`Loading: ${(progress * 100).toFixed(0)}% - ${text}`);
});
error
Fired when an error occurs.
waypointChange
Fired when the current waypoint changes.
viewer.on('waypointChange', ({ index, waypoint }) => {
console.log(`Arrived at waypoint ${index}: ${waypoint.name}`);
updateUI(waypoint);
});
playbackStart
Fired when playback begins.
playbackStop
Fired when playback stops or pauses.
frameChange
Fired when the current frame changes (4DGS only).
viewer.on('frameChange', (frame: number, total: number) => {
progressBar.value = (frame / (total - 1)) * 100;
frameCounter.textContent = `${frame + 1} / ${total}`;
});
frameComplete
Fired when 4DGS playback reaches the end (non-looping mode only).
modeChange
Fired when camera mode changes.
viewer.on('modeChange', ({ mode }) => {
console.log(`Camera mode: ${mode}`); // 'orbit', 'explore', 'tour'
});
Example: Custom Progress UI
const progressBar = document.getElementById('progress');
const statusText = document.getElementById('status');
viewer.on('progress', ({ progress, text }) => {
progressBar.style.width = `${progress * 100}%`;
statusText.textContent = text;
});
viewer.on('ready', () => {
progressBar.parentElement.style.display = 'none';
});
Example: 4DGS Playback Controls
const playBtn = document.getElementById('play');
const slider = document.getElementById('timeline');
const counter = document.getElementById('counter');
playBtn.onclick = () => {
if (viewer.isPlaying()) {
viewer.pause();
} else {
viewer.play();
}
};
slider.oninput = (e) => {
viewer.setFrameProgress(e.target.value / 100);
};
viewer.on('frameChange', (frame, total) => {
counter.textContent = `${frame + 1} / ${total}`;
slider.value = (frame / (total - 1)) * 100;
});
viewer.on('playbackStart', () => {
playBtn.textContent = '⏸ Pause';
});
viewer.on('playbackStop', () => {
playBtn.textContent = '▶ Play';
});
Example: Waypoint Tour UI
const waypointName = document.getElementById('waypoint-name');
const waypointDesc = document.getElementById('waypoint-desc');
const prevBtn = document.getElementById('prev');
const nextBtn = document.getElementById('next');
prevBtn.onclick = () => viewer.prevWaypoint();
nextBtn.onclick = () => viewer.nextWaypoint();
viewer.on('waypointChange', ({ index, waypoint }) => {
waypointName.textContent = waypoint.name;
waypointDesc.textContent = waypoint.description || '';
// Update button states
prevBtn.disabled = index === 0;
nextBtn.disabled = index === viewer.getWaypointCount() - 1;
});