<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"]);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.