<div class="gallery loading"></div>

<div class="controls">
    <div class="previous button"></div>
    <div class="play-state button paused"></div>
    <div class="next button"></div>
</div>
body {
    transform-style: preserve-3d;
    perspective: 200em;
    perspective-origin: center -50%;
        
    width: 100%;
    height: 100vh;
    
    background-color: black;
        
    overflow: hidden;
}

.gallery {
    --loader-size: 3em;
    --loader-size-2: calc(var(--loader-size) * 2);
    --rotation: 0deg;
    
    position: absolute;

    left: 50%;
    top: 40%;
    
    transform: translate(-50%, -50%) rotateY(var(--rotation));
    transform-style: preserve-3d;
    
    width: 30em;
    height: 20em;
    
    max-width: 100%;
    max-height: 100vh;
        
    counter-reset: panels;
    
    transition: transform 1s;
    
    user-select: none;
}

.gallery.loading {
    overflow: hidden;
}

.gallery.loading::before {
    content: "Loading...";
    
    position: absolute;
    
    left: 50%;
    top: 50%;
    
    display: inline-block;
    
    width: var(--loader-size-2);
    height: var(--loader-size);
    
    text-align: center;
    line-height: 3em;
    font-size: 3em;
    
    border: 10px solid white;
    border-radius: 100%;
    
    color: white;
    
    transform: translate3d(-50%, -50%, 0px);
}

.gallery.auto-spin {
    transition: none;
    
    animation: spin 30s linear 0s infinite;
}

@keyframes spin {
    from {
        transform: translate(-50%, -50%) rotateY(var(--rotation));
    }
    
    to {
        transform: translate(-50%, -50%) rotateY(calc(360deg + var(--rotation)));
    }
}

.gallery.loading .panel {
    display: none;
}

.panel {
    position: absolute;

    width: 100%;
    height: 100%;

    text-align: center;
    font-weight: bold;
    line-height: 20em;
    
    backface-visiblity: hidden;
    
    transition: width 1s, height 1s;
    
    user-select: none;
}

.panel:nth-child(odd) {
    background-color: rgba(0, 0, 255, 0.25)
}

.panel:hover {
    background-color: rgba(255, 0, 0, 0.25);
}

.panel img {
    position: absolute;
    
    left: 0;
    top: 0;
    
    width: 100%;
    height: 100%;
    
    user-select: none;
}

.controls {
    position: absolute;
    
    left: 50%;
    bottom: 5%;
    
    transform: translateX(-50%);
    
    width: 20em;
    
    padding: 0.25em;
    
    text-align: center;
    
    background-color: rgba(128,128,128, 0.25);
    
    border-radius: 5px;
    
    animation: fade-out 5s;
    animation-fill-mode: forwards;
}

.controls:hover {
    animation: none;
    animation-fill-mode: none;
}

@keyframes fade-out {    
    75% {
        opacity: 1;
    }
    
    to {
        opacity: 0;
    }
}

.controls .button {
    positon: absolute;
    
    display: inline-block;
    
    width: 5em;
    height: 2em;
    
    padding: 0.25em;
    
    text-align: center;
    line-height: 2em;
    font-weight: bold;
    
    border-radius: 5px;
    
    background-color: lightgrey;
    
    transition: color 0.5s, background-color 0.5s;
    
    cursor: pointer;
}

.controls .button:hover {
    color: white;
    
    background-color: #808080;
}

.controls .button::before {
    margin-top: 0.1em;
}

.next {
    float: right;
}

.next::before {    
    content: "\2B05";
    
    display: inline-block;
    
    transform: scale(-1.75, 2);
}

.previous {
   float: left;
}

.previous::before {
    content: "\2B05";
    
    display: inline-block;
    
    transform: scale(1.75, 2);
}

.play-state {
    width: 3em;
}

.play-state::before {
    content: "\2759 \2759";
    
    display: inline-block;
    
    transform: scale(2);
}

.play-state.paused::before {
    content: "\25B6";
}
var gallery = document.querySelector(".gallery"), CS = getComputedStyle(gallery);

var images = [], total = 0, loaded = 0, failed = 0;

function createPanel(img, angle, x, z)
{
    let panel = document.createElement("div");
    panel.className = "panel";
    
    panel.style.transform = `translateX(${x}px) translateZ(${z}px) rotateY(${angle}deg)`;
        
    panel.appendChild(img);
    
    gallery.appendChild(panel);
}

var rotation = 0, startTime = 0;

function createGallery()
{
    let width = gallery.clientWidth + 16;
    
    let deg = 360 / total, rad = deg * Math.PI / 180;
    let perimeter = width * total;
    let apothem = width / (2 * Math.tan(Math.PI / total));
    
    let x, z;
        
    for(var i = 0; i < total; i++)
    {
        x = Math.sin(rad * i) * apothem;
        z = Math.cos(rad * i) * apothem;
        
        createPanel(images[i], deg * i, x, z);
    }
    
    gallery.classList.remove("loading");
}

function rotateGallery(deg)
{    
    gallery.style.setProperty("--rotation", `${deg}deg`);
}

function loadImage(e)
{
    if(e.type == "error")
        failed++;
    else if(e.type == "load")
    {
        images.push(e.target);
        
        loaded++;
    }
    
    if(loaded + failed >= total)
        createGallery();
}

function loadImages(sources)
{
    total = sources.length;
    
    sources.forEach((src) => {
        let img = new Image()

        img.onload = loadImage;
        img.onerror = loadImage;

        img.src = src;
    });
}

document.querySelector(".previous").addEventListener("click", (e) => {
    if(gallery.classList.contains("auto-spin") || gallery.classList.contains("loading"))
        return;
    
    rotation += -(360 / total);
    
    rotateGallery(rotation);
});

document.querySelector(".next").addEventListener("click", (e) => {
    if(gallery.classList.contains("auto-spin") || gallery.classList.contains("loading"))
        return;
    
    rotation += (360 / total);
    
    rotateGallery(rotation);
});

document.querySelector(".play-state").addEventListener("click", (e) => {
    if(gallery.classList.contains("loading"))
        return;
    
    e.currentTarget.classList.toggle("paused");
    
    // Snap to the panel closest to the front based on animation duration
    if(gallery.classList.contains("auto-spin"))
    {
        let endTime = performance.now();
        
        let spinDuration = (endTime - startTime) / 1000;
        
        let panelDuration = parseInt(CS.getPropertyValue("animation-duration")) / total;
        
        let angleDelta = 360 / total;
        
        let oldPanel = rotation / angleDelta, change = Math.round(spinDuration / panelDuration);
        
        rotation += angleDelta * change * (rotation < 0 ? -1 : 1);
        
        rotateGallery(rotation);
    }
    else
        startTime = performance.now();
    
    gallery.classList.toggle("auto-spin");
    
    let deg = parseInt(CS.getPropertyValue("--rotation"));
    
    if(deg < 0)
        gallery.style.animationDirection = "backwards";    
});

// DISCLAIMER: All images were found on the internet and I have no claim to them whatsoever

addEventListener("load", () => {
    loadImages(["https://images.pexels.com/photos/257360/pexels-photo-257360.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    "https://images.pexels.com/photos/355296/pexels-photo-355296.jpeg?cs=srgb&dl=blur-branches-daylight-355296.jpg&fm=jpg",
    "https://cdn.pixabay.com/photo/2017/10/25/12/13/landscapes-2887796_960_720.jpg", 
    "https://images.all-free-download.com/images/graphiclarge/beautiful_nature_landscape_05_hd_picture_166223.jpg", 
    "https://www.iucn.org/sites/dev/files/content/images/2020/shutterstock_1458128810.jpg", 
    "https://images2.alphacoders.com/689/689529.jpg"]);    
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.