Hi @doordewar,
Thank you for posting this thread because it was a great challenge!
Here is my take on this:
The first thing was to get 100 images from an API somewhere and here is a useful one: https://picsum.photos/
Using this bash script to do download them automatically:
# download.sh
set -B
for i in {1..100}; do
let id=i*2
wget -O $i.png https://picsum.photos/id/$id/200
done
$ bash download.sh
Then this is a schema of the different variables and the layout:
The idea is to display the images linearly and have an infinite scroll. The first step was to have a working scroll effect with the mouse which I did with simple velocity/acceleration stuff.
Next was to display the current scroll position with a scroll bar, for this the map()
function is quite useful.
Last step was to load/unload images as soon as you scroll too far. This was the most tricky part but I figured out the way to do this with a LinkedList to easily add or remove images.
So the images are loaded and removed as chunks (of 5 for example) as soon as you go too far so you only have a fixed amount of loaded images at every time.
This is the final code:
// Joseph HENRY
// https://discourse.processing.org/t/infinite-scrolling/34028
import java.util.LinkedList;
// Images are stored in a LinkedList for FIFO insert and removal
LinkedList<PImage> images;
int totalImages = 100;
// The amount of images loaded in memory
int imagesBufferSize = 10;
// Individual image height
int imageHeight = 200;
// The total height of images
int totalHeight = totalImages * imageHeight;
// Image offset of scroll
int imagesOffset = 0;
// Chunk size of images loaded/unloaded when scrolling
int imagesChunkLoadingSize = 5;
// The scroll position in pixels
int scrollPosition = 0;
// Scroll velocity added to the position
float scrollVelocity = 0;
// Scroll acceleration added to the velocity
float scrollAcceleration = 0;
int scrollBarWidth = 10;
/**
* Taken from: https://easings.net/#easeOutCubic
*/
float easeOutCubic(float x) {
return 1 - pow(1 - x, 3);
}
/**
* Loads an image at the specified index
*/
PImage loadImageAtIndex(int i) {
return loadImage(str(i) + ".png");
}
void setup() {
size(200, 500);
images = new LinkedList<PImage>();
// First load a chunk of images
for (int i = 1; i <= imagesBufferSize; i++) {
images.add(loadImageAtIndex(i));
}
}
void displayScrollBar() {
// Scrollbar backdrop
noStroke();
fill(0, 200);
int scrollBarX = width - scrollBarWidth;
rect(scrollBarX, 0, scrollBarWidth, height);
// Loaded area bar
float loadedAreaY = map(imagesOffset * imageHeight, 0, totalHeight, 0, height);
float loadedAreaHeight = map(images.size(), 0, totalImages, 0, height);
fill(#9664A0);
rect(scrollBarX, loadedAreaY, scrollBarWidth, loadedAreaHeight, 10);
// Scroll handle bar
int visibleSize = height;
float scrollHandleHeight = (float) visibleSize / totalHeight * height;
float scrollHandlePosition = map(scrollPosition, 0, totalHeight - height, 0, height - scrollHandleHeight);
fill(#7982DE);
rect(scrollBarX, scrollHandlePosition, scrollBarWidth, scrollHandleHeight, 10);
}
void handleImageLoading() {
// Compute the limits of the loaded images
int firstImageYPosition = imagesOffset * imageHeight;
int lastImageYPosition = firstImageYPosition + images.size() * imageHeight;
// Determine if we are close to the limits
boolean closeToFirstImage = scrollPosition < firstImageYPosition + imageHeight / 2.0;
boolean closeToLastImage = scrollPosition > lastImageYPosition - height - imageHeight / 2.0;
if (closeToFirstImage || closeToLastImage) {
int offsetAdd = 0;
for (int i = 0; i < imagesChunkLoadingSize; i++) {
int nextImageIndex = closeToFirstImage ? imagesOffset - i : images.size() + imagesOffset + 1 + i;
// If the image can be loaded (we are not at the boundaries)
if (nextImageIndex >= 1 && nextImageIndex <= totalImages) {
PImage img = loadImageAtIndex(nextImageIndex);
// Append at the end or the beginning depending on the direction
if (closeToFirstImage) {
images.addFirst(img);
images.removeLast();
} else {
images.addLast(img);
images.removeFirst();
}
offsetAdd += (closeToFirstImage ? -1 : 1);
}
}
// Update the image offset
imagesOffset += offsetAdd;
}
}
void updateScrollPhysics() {
// Introduce drag in acceleration and velocity
scrollAcceleration *= 0.9;
scrollVelocity += scrollAcceleration;
scrollVelocity *= 0.95;
scrollPosition += scrollVelocity;
// Limit the scroll min and max value
scrollPosition = constrain(scrollPosition, 0, totalHeight - height);
}
void displayImages() {
for (int i = 0; i < images.size(); i++) {
PImage img = images.get(i);
image(img, 0, (i + imagesOffset) * imageHeight - scrollPosition);
}
}
void draw() {
background(255);
displayImages();
displayScrollBar();
handleImageLoading();
fill(255, 0, 0);
textSize(15);
text("Loaded " + images.size() + " images", 10, 30);
updateScrollPhysics();
}
void mouseDragged() {
// When dragged use the cursor movements
scrollPosition -= mouseY - pmouseY;
}
void mouseReleased() {
// Compute the cursor y diff
int diffY = -(mouseY - pmouseY);
// Cancel velocity when going in the other direction
if (!(scrollAcceleration < 0 && diffY < 0) || !(scrollAcceleration > 0 && diffY > 0)) {
scrollVelocity = 0;
}
// Add movement to the scroll
scrollVelocity = diffY / 10.0;
scrollAcceleration = diffY / 5.0;
}
Have fun!