Infinite Circular Motion Animation
Overview
This animation creates an infinite circular motion effect of images arranged in a carousel. The images rotate around a fixed point on the screen, simulating a circular path. Navigation buttons allow scrolling through images smoothly. Clicking an image opens it in a lightbox for enlarged viewing.
HTML Structure
- Container: Wraps the entire carousel and controls.
- Navigation Buttons: Two buttons (prev and next) to rotate the carousel left or right.
- Carousel: Contains multiple .image-wrapper elements, each holding an image and a caption.
- Top Center Text: Displays the caption of the image currently closest to the top center.
- Lightbox: A modal overlay that shows the clicked image in larger form with a close button.
<div class="container">
<button class="nav-button" id="prev">⇠</button>
<button class="nav-button" id="next">⇢</button>
<div class="carousel">
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/hand-drawn-flat-design-mountain-landscape_23-2149158786.jpg"><span>Mountain Landscape</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/hand-drawn-flat-mountain-landscape_23-2149174187.jpg"><span>Hill View</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/gradient-mountain-landscape_23-2149162007.jpg"><span>Evening Landscape</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/illustrated-blue-gradient-tropical-forest-landscape_23-2148262369.jpg"><span>Forest Landscape</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/background-with-mountain-landscape-flat-design_23-2148299136.jpg"><span>Avatar Landscape</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/elegant-beautiful-sunset-scene-background-with-palm-tree_1055-17708.jpg"><span>Sunset Background</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/seaside-white-birds-colorful-background-watercolor-vector_53876-157807.jpg"><span>White Birds</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/illustrated-landscape-with-mountains-forest_23-2148262368.jpg"><span>Mountain and River</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/hand-drawn-adventure-background_23-2149051021.jpg"><span>Adventure Background</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/flat-design-mountain-landscape_23-2149172160.jpg"><span>Sunrise Landscape</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/artistic-gradient-mountains-landscape_23-2148261005.jpg"><span>Gradient Landscape</span></div>
<div class="image-wrapper"><img src="https://img.freepik.com/free-vector/hand-drawn-rural-landscape-background_52683-123607.jpg"><span>Rural Landscape</span></div>
</div>
<div id="top-center-text"></div>
</div>
<div id="lightbox">
<span id="close">×</span>
<img id="lightbox-img" src="">
</div>
CSS Styling
- Body uses flexbox for centering with dark background and white text.
- Carousel and images use absolute positioning to enable circular rotation.
- Smooth transformations (transform 0.5s ease) enable animated rotation.
- Navigation buttons are fixed on the sides with semi-transparent backgrounds.
- Lightbox is hidden by default and shown as a fixed overlay when active.
- Image captions are hidden by default except for the one near the top center.
body {
margin: 0;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background: #222;
overflow: hidden;
color: #fff;
}
.container {
position: relative;
text-align: center;
}
.carousel {
position: relative;
width: 800px;
height: 400px;
margin-bottom: 20px;
}
.image-wrapper {
position: absolute;
top: 150px;
left: 350px;
width: 100px;
height: 100px;
transition: transform 0.5s ease;
cursor: pointer;
}
.image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.image-wrapper span {
display: none;
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.6);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
.nav-button {
display: flex;
align-items: center;
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
font-size: 20px;
color: #fff;
border: none;
padding: 10px 15px;
cursor: pointer;
z-index: 10;
}
#prev {
left: 10px;
}
#next {
right: 10px;
}
#top-center-text {
text-align: center;
font-size: 1.2em;
color: #fff;
margin-bottom: 20px;
}
#lightbox {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
justify-content: center;
align-items: center;
}
#lightbox img {
max-width: 50%;
max-height: 50%;
}
#close {
position: absolute;
top: 20px;
right: 20px;
color: #fff;
font-size: 30px;
cursor: pointer;
}
JavaScript Functionality
Key Variables
- radius: Radius in pixels of the circular path for the images.
- angleStep: Degree step between each image on the circle (30 degrees).
- currentRotation: The current rotation angle offset for the carousel.
Normalizing Rotation
A helper function ensures angles wrap between -180 and 180 degrees for proper visibility calculations updateCarousel();
This function: updateCarousel();
- Calculates each image's angle plus current rotation.
- Normalizes angle.
- Sets visibility based on angle range (-90° to 90° visible).
- Rotates each image around the circle using CSS transform: rotate + translateY for radial positioning.
- Tracks which image is closest to the top center (angle near 0°) to update the caption displayed in the top center text.
- Navigation Event Listeners
- Next button decreases currentRotation by angleStep to rotate clockwise.
- Prev button increases currentRotation by angleStep to rotate counterclockwise.
- Both trigger updateCarousel() with smooth transform animations.
Lightbox Functionality
- Clicking an image opens the lightbox with that image's source.
- Clicking the close button or outside the image closes the lightbox.
const prevButton = document.getElementById("prev");
const nextButton = document.getElementById("next");
const topCenterText = document.getElementById("top-center-text");
const images = Array.from(document.querySelectorAll(".image-wrapper"));
const lightbox = document.getElementById("lightbox");
const lightboxImg = document.getElementById("lightbox-img");
const closeButton = document.getElementById("close");
const radius = 250;
const angleStep = 30;
let currentRotation = -90;
function normalizeRotation(rotation) {
rotation = (rotation + 180) % 360;
if (rotation < 0) rotation += 360;
return rotation - 180;
}
function updateCarousel() {
let closestAngle = 180;
let topImage = null;
images.forEach((img, index) => {
let angle = index * angleStep + currentRotation;
angle = normalizeRotation(angle);
const visible = angle >= -90 && angle <= 90;
img.style.visibility = visible ? "visible" : "hidden";
img.style.transform = `rotate(${angle}deg) translate(0, -${radius}px)`;
if (Math.abs(angle) < closestAngle) {
closestAngle = Math.abs(angle);
topImage = img;
}
});
// Hide all spans
images.forEach((img) => (img.querySelector("span").style.display = "none"));
if (topImage) {
const spanText = topImage.querySelector("span").textContent;
topCenterText.textContent = spanText;
// topImage.querySelector('span').style.display = 'block';
}
}
// Next button
nextButton.addEventListener("click", () => {
currentRotation -= angleStep;
images.forEach((img) => (img.style.transition = "transform 0.5s ease"));
updateCarousel();
});
// Prev button
prevButton.addEventListener("click", () => {
currentRotation += angleStep;
images.forEach((img) => (img.style.transition = "transform 0.5s ease"));
updateCarousel();
});
// Lightbox functionality
images.forEach((img) => {
img.addEventListener("click", () => {
lightbox.style.display = "flex";
lightboxImg.src = img.querySelector("img").src;
});
});
closeButton.addEventListener("click", () => (lightbox.style.display = "none"));
lightbox.addEventListener("click", (e) => {
if (e.target === lightbox) lightbox.style.display = "none";
});
// Initialize
updateCarousel();
This structure allows an infinite circular animation of images with intuitive user controls and an accessible lightbox for enhanced viewing.
Working DEMO :
Codepen Demo
Top comments (0)