Frontend Development 11 min read

Implementing VR Seat Selection for Airline Cabins Using Photo-Sphere-Viewer

This article details a front‑end solution for online seat selection in a virtual‑reality airplane cabin, analyzing challenges such as marker visibility, scene transition effects, and efficient point placement, and presenting concrete implementations with Photo‑Sphere‑Viewer, custom zoom animations, and marker management code.

Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Implementing VR Seat Selection for Airline Cabins Using Photo-Sphere-Viewer

Background – A special requirement arose to enable online seat selection for an airline cabin within a VR environment. The difficulty lay in correctly displaying all seat markers, handling interactions, and providing smooth scene transitions. After reviewing available technologies, the team chose the open‑source Photo‑Sphere‑Viewer, a Three.js‑based framework.

Photo‑Sphere‑Viewer – This library renders a 360°×180° spherical panorama, supports point addition, query, and removal via a clear API, and can switch between canvas and WebGL rendering modes with good performance and compatibility.

Problem 1: Correctly displaying all seat markers – A single panoramic image causes near seats to appear large and distant seats to be too small, leading to overlapping markers. The solution is to use multiple panoramic images, each focusing on a limited set of rows, and provide navigation markers to switch between them.

Problem 2: Transition effects between panoramas – Photo‑Sphere‑Viewer lacks built‑in transition animations. The implemented approach simulates forward movement by gradually increasing the zoom level (from 20 to 40) and resetting it, while backward movement decreases the zoom.

Problem 3: Efficient marker placement across multiple images – Seat positions are expressed in latitude/longitude ranges. By fixing the camera angle and image size across panoramas, the same coordinates can be reused, and a helper tool records coordinates via click events, greatly speeding up marker setup.

Details and public markers – Each panorama includes common navigation markers (forward/backward). Edge panoramas omit markers that would point outside the scene (e.g., no forward marker on the first image).

Main code logic – Updating markers on scene change

/**
 * Update markers when switching scenes
 * @param {String} type
 * @param {Array}  initMarkers
 */
upDateMarkers(type, initMarkers){
    const that = this;
    if(type === 'forward'){
        that.curLineTeamIndex = that.curLineTeamIndex >= that.seatTeamMap.length ? that.seatTeamMap.length : that.curLineTeamIndex + 1;
    }else if(type === 'back'){
        that.curLineTeamIndex = that.curLineTeamIndex <= 0 ? 0 : that.curLineTeamIndex - 1;
    }
    let oldMarkers = initMarkers || that.curTeamMarkers,
        newMarkers = that.seatTeamMap[that.curLineTeamIndex];
    for(let i = 0; i < newMarkers.length; i++){
        if(!viewer.getMarker(newMarkers[i].id)){
            // ...add seat marker
        }else {
            viewer.updateMarker({id: newMarkers[i].id, visible: true}, true);
        }
        if (oldMarkers[i]) {
            oldMarkers[i].visible = false;
            viewer.updateMarker(oldMarkers[i], false);
        }
    }
    that.curTeamMarkers = newMarkers;
}

Seat selection state control

if(that.personTeams.some(seat => seat.seat === marker.seat)){
    return;
}else if(marker.seatSymbol === "C"){
    that.openToast('该座位已经被选,不能选择!');
    return;
}else if(that.personTeams[that.curPersonIndex].selected){
    document.querySelector('.loader').style.opacity = 1;
    let _oldMarker = viewer.getMarker(that.personTeams[that.curPersonIndex].seat);
    _oldMarker.selected = false;
    // ...update old marker style
    viewer.updateMarker(_oldMarker, true);
    that.personTeams[that.curPersonIndex].seat = marker.seat;
}else{
    document.querySelector('.loader').style.opacity = 1;
    that.personTeams[that.curPersonIndex].selected = true;
    that.personTeams[that.curPersonIndex].seat = marker.seat;
}

Zoom animation to simulate forward/backward movement

var zoomAnimate, _zoom;
if(type === "pre"){
    zoomAnimate = setInterval(() => {
        _zoom = this.getZoomLevel();
        this.zoom(_zoom + 2, true);
        if(_zoom >= 40){
            clearInterval(zoomAnimate);
            this.zoom(20, true);
            this.render();
            r();
        }
    }, 50);
}else if(type === "last"){
    zoomAnimate = setInterval(() => {
        _zoom = this.getZoomLevel();
        this.zoom(_zoom - 2, true);
        if(_zoom <= 0){
            clearInterval(zoomAnimate);
            this.zoom(20, true);
            this.render();
            r();
        }
    }, 50);
}

API modification – The original getMarker API threw an error when a marker was missing; it was changed to return false instead, simplifying error handling.

/**
 * @summary Returns the internal marker object for a marker id
 * @param {*} markerId
 * @returns {PSVMarker | Boolean}
 */
PSVHUD.prototype.getMarker = function(markerId) {
    var id = typeof markerId === 'object' ? markerId.id : markerId;
    if (!this.markers[id]) {
        return false;
        /* throw new PSVError('cannot find marker "' + id + '"'); */
    }
    return this.markers[id];
};

Initial rendering with common markers – The viewer is instantiated with a configuration that includes the panorama source, transition settings, and a dynamically built markers array containing both public navigation points and seat markers.

viewer = new PhotoSphereViewer({
    container: 'viewer',
    panorama: that.scenesMap[that.curIndex],
    time_anim: false,
    transition: {duration: 1, loader: false},
    move_speed: 5,
    navbar: false,
    mousewheel: false,
    memberId: '',
    openId: '',
    touchmove_two_fingers: true,
    move_inertia: false,
    loading_img: '',
    loading_txt: '',
    markers: (function(){
        var a = [];
        // ...add public markers
        that.markers = a;
        return a;
    })(),
});

The article concludes with a demo link showcasing the final VR seat‑selection effect.

frontendWeb DevelopmentThree.jsVRseat-selectionPhoto Sphere Viewer
Tongcheng Travel Technology Center
Written by

Tongcheng Travel Technology Center

Pursue excellence, start again with Tongcheng! More technical insights to help you along your journey and make development enjoyable.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.