centered carousel
by kthornbloom
HTML
<div class="yea">
<div class="yea-stage">
<div class="yea-item">
<img class="yea-img" src="https://images.unsplash.com/photo-1504639725590-34d0984388bd?h=300&w=200&q=80"/>
<div class="yea-text"><b>1 Title</b><br> description goes here.</div>
</div>
<div class="yea-item">
<img class="yea-img" src="https://images.unsplash.com/photo-1504639725590-34d0984388bd?h=300&w=200&q=80"/>
<div class="yea-text"><b>2 Title</b><br> description goes here description goes here description goes here.</div>
</div>
<div class="yea-item">
<img class="yea-img" src="https://images.unsplash.com/photo-1504639725590-34d0984388bd?h=300&w=200&q=80"/>
<div class="yea-text"><b>3 Title</b><br> description goes here.</div>
</div>
<div class="yea-item">
<img class="yea-img" src="https://images.unsplash.com/photo-1504639725590-34d0984388bd?h=300&w=200&q=80"/>
<div class="yea-text"><b>4 Title</b><br> description goes here.</div>
</div>
<div class="yea-item">
<img class="yea-img" src="https://images.unsplash.com/photo-1504639725590-34d0984388bd?h=300&w=200&q=80"/>
<div class="yea-text"><b>5 Title</b><br> description goes here.</div>
</div>
</div>
</div>
<button id="next">Next</button>
<button id="prev">Prev</button>
CSS
.yea {
position: relative;
border: 1px solid red;
}
.yea-stage {
border: 1px solid blue;
display: flex;
flex-direction: row;
justify-content: center;
gap: 1rem;
max-width: 100%;
overflow: hidden;
padding-bottom: 100px;
}
.yea-img {
aspect-ratio: 1.25/2;
object-fit: cover;
border-radius: 4px;
}
.yea-center {
background: red;
}
.yea-item {
position: relative;
transition: .5s;
}
.yea-center-prev,
.yea-center-next {
opacity: .7;
transform: scale(.9);
}
.yea-item:not(.yea-center):not(.yea-center-prev):not(.yea-center-next){
opacity: .5;
transform: scale(.7);
margin: 0 -2em;
}
.yea-text {
position: absolute;
top: 100%;
left: 0;
width: 100%;
}
JavaScript
/**
* Highlights center + adjacent items with smooth transitions
* @param {number} offset - 0 = center, -1 = previous, +1 = next (only one gets the special class)
*/
function defineMiddle(offset = 0) {
const stage = document.querySelector('.yea-stage');
if (!stage) return;
const items = stage.querySelectorAll('.yea-item');
if (items.length === 0) return;
const centerIndex = Math.floor(items.length / 2);
const targetIndex = Math.max(0, Math.min(items.length - 1, centerIndex + offset));
const newCenterItem = items[targetIndex];
// Determine which class to add based on offset
const classToAdd =
offset === 0 ? 'yea-center' :
offset === -1 ? 'yea-center-prev' :
offset === 1 ? 'yea-center-next' : 'yea-center';
// 1. Immediately add the new class → incoming animation starts now
newCenterItem.classList.add(classToAdd);
// 2. On next frame, remove the same class from all other items
requestAnimationFrame(() => {
stage.querySelectorAll('.yea-center, .yea-center-prev, .yea-center-next').forEach(el => {
if (el !== newCenterItem) {
el.classList.remove('yea-center', 'yea-center-prev', 'yea-center-next');
}
});
});
}
defineMiddle();
const prev = document.getElementById('prev'),
next = document.getElementById('next');
function yeaGoForward() {
defineMiddle(-1);
const stage = document.querySelector('.yea-stage');
if (!stage || stage.querySelectorAll('.yea-item').length <= 1) return;
const firstItem = stage.children[0];
const itemWidth = firstItem.offsetWidth;
// Step 1: Pre-apply classes for the incoming center (the current left-1 becomes center)
// This already reflects current state
// No need to pre-call — just let the slide happen and reassign at end
// Step 2: Animate slide
stage.style.transition = 'transform 0.45s ease-in-out';
stage.style.transform = `translateX(-${itemWidth}px)`;
// Step 3: After...