So, I’m using Pannellum to display panoramas. On my actual site, some of the different views are static images, while some are panoramas. Many of them have videos to transition between them, which have been produced so that they line up exactly with the static images and the initial views of the panoramas. However, this only works in the 16:9 aspect ratio the images and videos were produced in.
It’s required that letterboxing is avoided, so images and videos are set to fill the viewport with object-fit: cover, meaning certain parts are cropped out on aspect ratios other than 16:9. I’ve been mostly testing with aspect ratios below that, as iPad is a key target device for the site, and on those aspect ratios the left and right sides of the image are cropped.
This causes issues as the video transitions between Pannellum and the next view no longer line up. With Pannellum set to the default hfov of 100, aspect ratios below 16:9 cause the top and bottom of the view to expand, keeping the exact same content in view. That extra visual content isn’t present in the 16:9 video though, so the cut to the video makes it appear to zoom in with a sudden jump.
Adjusting the hfov in the Pannellum viewer config resolves this, but the hfov value needed will be different for every aspect ratio, and I can’t seem to find a formula for dynamically setting the hfov based on aspect ratio.
Below is a simplified example to demonstrate the issue. Here I’ve just used a static image to represent the video transitions, but it works just the same. At a 16:9 resolution, without moving or zooming the panorama at all, the two line up perfectly. Adjust the aspect ratio towards 1, though, past the point where the image can no longer scale to fit, and you’ll see the ‘zoom in’ behaviour when toggling between the two.
CodePen:
https://codepen.io/Carces/pen/GRaqybR
HTML:
<head>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/pannellum.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/build/pannellum.css"
/>
</head>
<body>
<button id="view-toggle" onclick="toggleView(event)">Show static</button>
<div class="content-container">
<img class="content" id="static-image" src="https://imgur.com/PRWoqea.jpeg" />
<div class="content" id="panorama"></div>
</div>
</body>
CSS:
* {
border: 0;
padding: 0;
margin: 0;
}
body {
height: 100vh;
width: 100vw;
overflow: hidden;
}
.content-container {
width: 100vw;
height: 100vh;
position: relative;
}
.content {
position: absolute;
z-index: 2;
top: 0;
left: 0;
width: 100vw;
height: 100%;
object-fit: cover;
object-position: bottom;
}
#view-toggle {
position: absolute;
top: 10px;
right: 10px;
z-index: 5;
padding: 5px;
background-color: blue;
color: white;
}
.behind {
z-index: 1 !important;
}
JavaScript:
document.addEventListener('DOMContentLoaded', (element) => {
pannellum.viewer('panorama', {
type: 'cubemap',
cubeMap: [
'https://corsproxy.io/?' + encodeURIComponent('https://imgur.com/kB1Pnew.jpeg'),
'https://corsproxy.io/?' + encodeURIComponent('https://imgur.com/ntgx3Oa.jpeg'),
'https://corsproxy.io/?' + encodeURIComponent('https://imgur.com/AIHdU5N.jpeg'),
'https://corsproxy.io/?' + encodeURIComponent('https://imgur.com/wbxfpx3.jpeg'),
'https://corsproxy.io/?' + encodeURIComponent('https://imgur.com/2FIEFm9.jpeg'),
'https://corsproxy.io/?' + encodeURIComponent('https://imgur.com/1PDezAB.jpeg'),
],
pitch: 0,
yaw: 0,
hfov: 100,
autoLoad: true,
autoRotate: 0,
showControls: false,
});
});
let currentView = 'panorama';
function toggleView(event) {
const staticImage = document.getElementById('static-image');
const panorama = document.getElementById('panorama');
if (currentView === 'static-image') {
panorama.classList.remove('behind');
staticImage.classList.add('behind');
currentView = 'panorama';
event.target.innerText = 'Show static'
} else {
staticImage.classList.remove('behind');
panorama.classList.add('behind');
currentView = 'static-image';
event.target.innerText = 'Show panorama'
}
}