Issue
I am working on a manual looping carousel in JavaScript, where position changes are managed through data-status attributes linked to CSS properties.
However, I'm encountering an issue with the transitions. When the carousel starts looping, elements appear from the opposite direction than they are supposed to.
video showcase on imgur: https://imgur.com/Lz1QOiQ
what am i doing wrong here ?
var articles = Array.from(document.querySelectorAll('.carousel article'));
var currentArticle = document.querySelector('[data-status="active"]');
var currentIndex = parseInt(currentArticle.dataset.index);
function swapRight() {
var nextArticleIndex = currentIndex + 1;
var nextArticle = document.querySelector(`[data-index="${nextArticleIndex}"]`);
if (nextArticle) {
console.log('swapped to next');
currentArticle.dataset.status = "afterToLeft";
nextArticle.dataset.status = "moveRight";
nextArticle.dataset.status = "active";
currentIndex = nextArticleIndex;
currentArticle = nextArticle;
} else {
restart();
console.log("Restarting");
console.log('currentIndex:', currentIndex);
console.log('nextArticleIndex:', 0);
}
}
function swapLeft() {
var nextArticleIndex = currentIndex - 1;
var nextArticle = document.querySelector(`[data-index="${nextArticleIndex}"]`);
if (nextArticle) {
console.log('swapped to previous');
currentArticle.dataset.status = "afterToRight";
nextArticle.dataset.status = "resetToLeft";
nextArticle.dataset.status = "active";
currentIndex = nextArticleIndex;
currentArticle = nextArticle;
} else {
swapToEnd();
console.log("Swapping to end");
console.log('currentIndex:', currentIndex);
console.log('nextArticleIndex:', 0);
}
}
function restart() {
var nextArticleIndex = 0;
var nextArticle = document.querySelector(`[data-index="${nextArticleIndex}"]`);
if (nextArticle) {
console.log('restarted');
currentArticle.dataset.status = "afterToRight";
nextArticle.dataset.status = "resetToLeft";
nextArticle.dataset.status = "active";
currentIndex = nextArticleIndex;
currentArticle = nextArticle;
} else {
console.error("Failed to restart");
console.log('currentIndex:', currentIndex);
console.log('nextArticleIndex:', nextArticleIndex);
}
}
function swapToEnd() {
var nextArticleIndex = currentIndex + 4;
var nextArticle = document.querySelector(`[data-index="${nextArticleIndex}"]`);
if (nextArticle) {
console.log('swapped to end');
currentArticle.dataset.status = "afterToRight";
nextArticle.dataset.status = "resetToLeft";
nextArticle.dataset.status = "active";
currentIndex = nextArticleIndex;
currentArticle = nextArticle;
} else {
console.error("Failed to restart");
console.log('currentIndex:', currentIndex);
console.log('nextArticleIndex:', nextArticleIndex);
}
}
* {
margin: 0;
box-sizing: border-box;
}
.wrapper {
min-width: 100vh;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background: #E9E4D9;
font-family: 'Rubik', sans-serif;
}
.container {
display: flex;
flex-direction: row;
gap: 1rem;
align-items: center;
justify-content: space-around;
}
button {
height: 5rem;
width: 5rem;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
border: none;
cursor: pointer;
}
.carousel {
overflow: hidden;
width: 540px;
height: 375px;
position: relative;
background: #F8F5F0;
}
article {
position: absolute;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 1rem;
justify-content: center;
gap: 1rem;
cursor: pointer;
transition: left 0.8s ease;
}
article img {
object-fit: cover;
width: 100%;
border-radius: 12px;
height: 200px;
}
article div h2 {
font-weight: 800;
background-color: #827055;
color: #CFC0A8;
padding: 10px;
font-size: 40px;
border-radius: 8px;
margin: 0;
}
article div p {
color: #B49D77;
font-size: 22px;
}
article div {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
article[data-status="unknown"] {
left: 100%;
}
article[data-status="active"] {
left: 0;
}
article[data-status="afterToLeft"] {
left: -100%;
}
article[data-status="afterToRight"] {
left: 100%;
}
article[data-status="resetToRight"] {
left: 100%;
transition: none;
opacity: 0;
}
article[data-status="reset"] {
left: 0;
transition: none;
opacity: 0;
}
article[data-status="moveRight"] {
left: 100%;
transition: none;
opacity: 0;
}
article[data-status="resetToLeft"] {
left: -100%;
}
<div class="wrapper">
<div class="container">
<button onclick="swapLeft()">
<svg xmlns="http://www.w3.org/2000/svg" height="2em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><style>svg{fill:#827055}</style><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
</button>
<div class="carousel" id="carousel">
<article data-status="active" data-index="0">
<img src="https://pause-maison.ouest-france.fr/wp-content/uploads/2023/08/deco-interieur-beige.jpg.webp" alt="img">
<div>
<h2>DAY 1</h2>
<p>friday, november 10th</p>
</div>
</article>
<article data-status="unknown" data-index="1">
<img src="https://cache.marieclaire.fr/data/photo/w1200_h630_ci/66/salon-beige.jpg" alt="img">
<div>
<h2>DAY 2</h2>
<p>saturday, november 11th</p>
</div>
</article>
<article data-status="unknown" data-index="2">
<img src="https://cache.marieclaire.fr/data/photo/w1200_h630_ci/66/salon-beige.jpg" alt="img">
<div>
<h2>DAY 3</h2>
<p>sunday, november 12th</p>
</div>
</article>
<article data-status="unknown" data-index="3">
<img src="https://cache.marieclaire.fr/data/photo/w1200_h630_ci/66/salon-beige.jpg" alt="img">
<div>
<h2>DAY 4</h2>
<p>monday, november 13th</p>
</div>
</article>
<article data-status="unknown" data-index="4">
<img src="https://cache.marieclaire.fr/data/photo/w1200_h630_ci/66/salon-beige.jpg" alt="img">
<div>
<h2>DAY 5</h2>
<p>tuesday, november 14th</p>
</div>
</article>
</div>
<button onclick="swapRight()">
<svg xmlns="http://www.w3.org/2000/svg" height="2em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><style>svg{fill:#827055}</style><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
</button>
</div>
</div>
Solution
Let me organize the flow of actions to preform the desired animation.
- A slide has been pushed to the left.
- Turn transition off
- Move slide to right
- Turn transitions on
- Move slide back into view
swap left and right when pressing the left arrow.
The way you have it set up, essentially everything happens at the same time. You need to give the browser time to move the position of the element before turning the animation back on. I do this by creating a helper function to wait or stall, and then wait an arbitrary 0ms for the browser to move things before turning the transitions back on. For my browser, this is enough time to render. This might accomplish the desired effect for you, however I'd bet most production image carrousels use copies of the desired element to create the illusion of a loop.
Hopefully this explanation helps! If you have any more questions, let me know.
const get = query => [...document.querySelectorAll(query)];
const wait = ms => new Promise(r => setTimeout(r, ms));
const slides = get(".slide");
let activeIndex = slides.findIndex(slide => slide.classList.contains("active"));
const lBtn = get("button.l")[0];
const rBtn = get("button.r")[0];
const incrementIndex = () => {
activeIndex++;
activeIndex = activeIndex % slides.length;
};
const decrementIndex = () => {
if (activeIndex == 0) activeIndex = slides.length;
activeIndex--;
};
const updateDisplay = async ({moveActiveLeft}) => {
const currentActive = get(".slide.active")[0];
currentActive.classList.remove("active");
const newClass = moveActiveLeft ? "left" : "right";
currentActive.classList.add(newClass);
const newActive = slides[activeIndex];
const incomingClass = moveActiveLeft ? "right" : "left";
newActive.classList.add("no-transition");
newActive.classList.remove(newClass);
newActive.classList.add(incomingClass);
//THIS IS THE MAGIC LINE
await wait(0);
newActive.classList.remove("no-transition");
newActive.classList.remove(incomingClass);
newActive.classList.add("active");
};
lBtn.addEventListener(
"click",
() => {
decrementIndex();
updateDisplay({ moveActiveLeft: false });
});
rBtn.addEventListener(
"click",
() => {
incrementIndex();
updateDisplay({moveActiveLeft: true});
});
* {
box-sizing: border-box;
}
body {
display: grid;
place-items: center;
margin: 0;
}
.wrapper {
border: 5px solid black;
height: 100dvh;
aspect-ratio: 1;
position: relative;
overflow: hidden;
}
.controls {
position: absolute;
bottom: 0; left: 0; right: 0;
display: grid;
grid-template-columns: 1fr 1fr;
}
.controls button {
font-size: 30pt; font-weight: bold;
}
.slide {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
background-color: var(--bg);
transition: translate 0.5s;
}
.a { --bg: red; }
.b { --bg: orange; }
.c { --bg: yellow; }
.slide:not(.active), .slide.right {
translate: 100% 0;
}
.slide.left {
translate: -100% 0;
}
.no-transition {
transition: none;
}
<div class="wrapper">
<div class="slide a active"></div>
<div class="slide b"></div>
<div class="slide c"></div>
<div class="controls">
<button class="l"> < </button>
<button class="r"> > </button>
<div>
</div>
Answered By - async await
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.