While keeping your same approach, this should work similarly as the example you provided. If you want the slideshow to keep it's width, all images should have the same width. Your error was not adding back the image that dissapeared to the container, at the end and with it's original width, or as in this case expanding again.
// From https://www.javascripttutorial.net/javascript-queue/
function Queue() {
this.elements = [];
}
Queue.prototype.enqueue = function(e) {
this.elements.push(e);
};
Queue.prototype.dequeue = function() {
return this.elements.shift();
};
Queue.prototype.isEmpty = function() {
return this.elements.length == 0;
};
Queue.prototype.peek = function() {
return !this.isEmpty() ? this.elements[0] : undefined;
};
Queue.prototype.length = function() {
return this.elements.length;
}
let images = new Queue();
document.querySelectorAll(".image").forEach(img => images.enqueue(img));
var totImg = images.length();
var first = images.dequeue();
var last;
var firstWidth = first.width;
var lastWidth = 0.0;
var bounding = first.getBoundingClientRect();
var originalWidth = first.width;
var lastOriginalWidth = 0.0;
var step = 1.0;
var lastStep = 0.0;
function LoopFunction() {
setInterval(galleryScroll, 20);
}
function galleryScroll() {
bounding = first.getBoundingClientRect();
if (bounding.left == 0) {
//console.log("immagine bordo sx");
if (first.width > 0) {
firstWidth -= step;
if (firstWidth < 0) {
firstWidth = 0;
}
first.style.width = firstWidth + 'px';
} else {
if (last && last.width != lastOriginalWidth) {
last.width = lastOriginalWidth;
}
let container = document.querySelector('.container');
container.removeChild(first);
last = first;
lastOriginalWidth = originalWidth;
last.width = 0.0;
lastWidth = 0.0;
images.enqueue(last);
container.appendChild(last);
first = images.dequeue();
originalWidth = first.width;
firstWidth = originalWidth;
lastStep = step * lastOriginalWidth / originalWidth;
}
}
if (last && last.width <= lastOriginalWidth) {
lastWidth += lastStep;
last.style.width = lastWidth + 'px';
if (last.width > lastOriginalWidth) {
last.width = lastOriginalWidth;
}
}
}
LoopFunction();
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}
<div class="container">
<img src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="" class="image">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="" class="image">
<img src="https://static.scientificamerican.com/sciam/cache/file/92E141F8-36E4-4331-BB2EE42AC8674DD3_source.jpg" alt="" class="image">
<img src="https://cdn.britannica.com/91/181391-050-1DA18304/cat-toes-paw-number-paws-tiger-tabby.jpg" alt="" class="image">
<img src="https://www.orlandocatcafe.com/wp-content/uploads/2020/07/14.png" alt="" class="image">
</div>
Update #1
As requested by OP, here is some further explenation of the changes I made:
I used a simple queue from javascripttutorial to have a data structure that reflects the objective, i.e. having images that move from right to left, and once they "exit" the scene, can get enqueued again.
Following this concept, we need pointers to both head and tail of the queue in order to update their animation, in particular first
, firstWidth
and originalWidth
are used respectively to point to the head, keep track of its current width during the animation and the originalWidth to which it should return when re-entering the scene. Similarly, this happens for the last
as well.
Once this is in place, our loop will be made of two parts, one that handles the head animation and moves the images in the queue when the head image reaches width == 0
and one that handles the tail re-entering the scene and expanding. To achieve continuity, we use a simple proportion after we have our new queue: step : originalWidth = lastStep : lastOriginalWidth
, which we use to determine at each frame how much respectively the head and tail images compress and expand. as step
is a fixed parameter, lastStep
is easily computed through simple arithmetics: lastStep = step * lastOriginalWidth / originalWidth
.
The code:
let container = document.querySelector('.container');
container.removeChild(first);
last = first;
lastOriginalWidth = originalWidth;
last.width = 0.0;
lastWidth = 0.0;
images.enqueue(last);
container.appendChild(last);
first = images.dequeue();
originalWidth = first.width;
is just a contextual switch over the changing the queue, where the pointers and respective data get update to reflect the changes in the data structure. So the first
becomes last
, the next image in the queue is the new compressing first
, as such originalWidth
shifts.
Let's go further
Now, to achieve your desired implementation, this still needs some work. First of all, the queue should only reflect the actual carousel, not all the images you have, so ideally you would have a first static data structure with all the images and respective metadata, in this case we mainly need their original width. then you would have a queue representing the images that are being animated.
Now, you can either reuse this code and change the width of the images to be all equal and fit the viewport you are using for your animation, which should work fine. The other option is to mirror the code for the head image to the tail, have a fixed step, so while the head compresses of step
pixels, the tail expands of the same step
pixels, and when the tail reaches maximum width, introduce a new image at the end of the queue which becomes the new tail.
To go in further detail on the last, the algorithm would look something like this:
- initialize static list of image containers with their respective width (
imgList
);
- initialize an empty queue (
imgQueue
);
- initialize a pointer pointing to the last image introduced in the queue (
listPtr
);
- get the width of the viewport your carousel should fill (
vpWidth
);
- while you have not filled such width, keep adding images to the queue and moving the
listPtr
left in the imgList
;
- have looping function similar to the one above but where the
if
over last
replicates similar steps to the first
ones;
This should be everything, I will work on the code in a while, now I need some sleep ;).
Update #2
Okay! Worked out something that actually works decently. If you have suggestions please let me know, in the meanwhile, here it is:
// From https://www.javascripttutorial.net/javascript-queue/
function Queue() {
this.elements = [];
}
Queue.prototype.enqueue = function(e) {
this.elements.push(e);
};
Queue.prototype.dequeue = function() {
return this.elements.shift();
};
Queue.prototype.isEmpty = function() {
return this.elements.length == 0;
};
Queue.prototype.first = function() {
return !this.isEmpty() ? this.elements[0] : undefined;
};
Queue.prototype.length = function() {
return this.elements.length;
};
// Added function to peek last element in queue
Queue.prototype.last = function() {
return !this.isEmpty() ? this.elements[this.elements.length - 1] : undefined;
};
// Added function that pops the head, enqueues it and returns it
Queue.prototype.shift = function() {
const head = this.dequeue();
this.enqueue(head);
return head;
}
// Returns a queue of HTMLElements based on the given class
function loadQueue(className) {
const rQueue = new Queue();
document.querySelectorAll('.' + className).forEach(image => rQueue.enqueue(image));
return rQueue;
}
// Queue of images to be added to the animation queue
const imgQueue = loadQueue('image');
// Images being animated inside the div
const imgAnimQueue = new Queue();
// To limit calls to the carousel
const carousel = $('.container');
function init() {
// Width of the viewport being used for the animation
// TODO: this shoud update whenever the page is resized, or at least the div
const vpWidth = carousel.width();
// Keeps track of the width of the images added to the animation queue
var currentFilledWidth = 0;
// Now that we have loaded the static information, let's clear the div
// that will be used as carousel, so that we can add the correct number
// of images back in
carousel.empty();
// Filling the animation queue
while (currentFilledWidth < vpWidth) {
// In order not to change the static data, we clone the image HTMLElement
const img = $(imgQueue.shift()).clone();
// Enqueuing the new image in the animation queue
imgAnimQueue.enqueue(img);
// Adding this image into the carousel
img.appendTo(carousel);
currentFilledWidth = currentFilledWidth + img.width();
// If we overflow the carousel width, we set the tail image to fill
if (currentFilledWidth > vpWidth) {
const overflow = currentFilledWidth - vpWidth;
img.width(img.width() - overflow);
currentFilledWidth -= overflow;
}
}
const step = 1;
var firstWidth = imgAnimQueue.first().width();
var lastWidth = imgAnimQueue.last().width();
// Now the loop can start
window.requestAnimationFrame(animateCarousel);
// Main function that animates the carousel
function animateCarousel() {
let first = imgAnimQueue.first();
let last = imgAnimQueue.last();
// If the image is still not disappeared, keep compressing it
if (firstWidth > 0) {
firstWidth -= step;
if (firstWidth < 0) firstWidth = 0;
first.width(firstWidth);
// Otherwise, remove it from the carousel and update all the data structs
} else {
first.remove();
imgAnimQueue.dequeue();
if (imgAnimQueue.first() != last) {
first = imgAnimQueue.first();
firstWidth = first.width();
} else {
first = last;
firstWidth = lastWidth;
imgAnimQueue.enqueue($(imgQueue.shift()).clone());
last = imgAnimQueue.last();
lastWidth = last.width();
last.appendTo(carousel);
}
}
if (lastWidth <= last.attr("data-original-width")) {
lastWidth += step;
// The image has completely expanded, let's introduce the next one
if (lastWidth >= last.attr("data-original-width")) {
last.width(last.attr("data-original-width"));
imgAnimQueue.enqueue($(imgQueue.shift()).clone());
last = imgAnimQueue.last();
last.width(0);
lastWidth = last.width();
last.appendTo(carousel);
// Otherwise just keep expanding it
} else {
last.width(lastWidth);
}
}
window.requestAnimationFrame(animateCarousel);
}
}
body {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100vw;
height: 100vh;
display: flex;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var loadedImages = 0;
const count = 4;
function loadImage(img) {
if (loadedImages != count) {
$(img).attr("data-original-width", $(img).width());
loadedImages += 1;
if (loadedImages == count) init();
}
}
</script>
<div class="container">
<img onload="loadImage(this)" src="https://media-cdn.tripadvisor.com/media/photo-s/0e/eb/ad/3d/crazy-cat-cafe.jpg" alt="1" class="image">
<img onload="loadImage(this)" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Chairman_Meow_Bao.jpg/1200px-Chairman_Meow_Bao.jpg" alt="2" class="image">
<img onload="loadImage(this)" src="https://cdn.britannica.com/91/181391-050-1DA18304/cat-toes-paw-number-paws-tiger-tabby.jpg" alt="3" class="image">
<img onload="loadImage(this)" src="https://www.orlandocatcafe.com/wp-content/uploads/2020/07/14.png" alt="4" class="image">
</div>