Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ClaytonSeager/InifinityScroll/llms.txt

Use this file to discover all available pages before exploring further.

All application logic for the Infinity Scroll gallery lives in a single file, Infinity.js. It is responsible for fetching photos from the Unsplash API, building the gallery DOM, tracking load state, responding to search input, driving infinite scroll, and managing the scroll-to-top button — with no external framework dependencies.

Configuration Constants

These const declarations at the top of Infinity.js control the core API behaviour. Edit them directly to change your API key or batch size.
KEY
string
required
Your Unsplash API Access Key. Obtain one by registering a developer application at https://unsplash.com/developers.
const KEY = 'YOUR_UNSPLASH_ACCESS_KEY';
count
number
default:"10"
Number of images fetched per API call. Increasing this value loads more images per batch but uses more API quota.
const count = 10;
query
string
default:"''"
The current search term. An empty string causes getApiUrl() to request random photos. When the user submits a search, handleSearch() overwrites this with the input value.
let query = '';
query is declared with let rather than const because handleSearch() reassigns it on every search submission.

State Variables

These module-level variables hold live runtime state. They are read and mutated by several functions throughout Infinity.js.
VariableTypeDefaultDescription
imageContainerHTMLElementReference to #Image__Container. Gallery <a>+<img> pairs are appended here.
loaderHTMLElementReference to #Loader. Its hidden property is toggled to show/hide the loading overlay.
readybooleanfalseGates the infinite-scroll trigger. Set to true only after all images in a batch have fired their load events. Reset to false when a new fetch begins.
imagesLoadednumber0Count of images that have fired their load event in the current batch. Compared against totalImages to detect batch completion.
totalImagesnumber0Total images in the batch currently being rendered. Set to photosArray.length at the start of displayPhotos().
photosArrayArray[]The last set of photo objects returned from the Unsplash API.
totalLoadedImagesnumber0Cumulative count of every image loaded this session across all batches. Displayed in #image-counter.
titleColumnHTMLElementReference to .Title__Column. Receives the scrolled class when scrollY > 50.
imageCounterHTMLElementReference to #image-counter. Its textContent is updated to totalLoadedImages after every image load.
scrollTopBtnHTMLElementReference to #scrollTop. Receives the visible class when scrollY > innerHeight.
searchInputHTMLElementReference to #search-input. Its .value is read by handleSearch() to set query.
searchButtonHTMLElementReference to #search-button. Has a click listener attached that calls handleSearch().

Functions

getApiUrl()

Constructs and returns the correct Unsplash API endpoint URL based on the current value of query.
  • When query is empty (default / random mode), it targets the random-photos endpoint: https://api.unsplash.com/photos/random/?client_id=${KEY}&count=${count}
  • When query is set (search mode), it targets the search endpoint: https://api.unsplash.com/search/photos/?query=${query}&client_id=${KEY}&count=${count}
Returns: string — the fully-formed API URL.
const getApiUrl = () => {
    const baseUrl = 'https://api.unsplash.com/photos/';
    const searchUrl = 'https://api.unsplash.com/search/photos/';
    const params = `client_id=${KEY}&count=${count}`;
    
    return query 
        ? `${searchUrl}?query=${query}&${params}`
        : `${baseUrl}random/?${params}`;
};

getPhotos() (async)

Fetches a batch of photos from Unsplash by calling getApiUrl(), then assigns the result to photosArray and triggers displayPhotos(). The Unsplash random and search endpoints return different JSON shapes:
  • Search (/search/photos/) wraps results in a results property: data.results
  • Random (/photos/random/) returns the array directly: data
getPhotos() handles this difference with a ternary on query. Errors are caught and logged to the console.
const getPhotos = async () => {
    try {
        const response = await fetch(getApiUrl());
        const data = await response.json();
        
        photosArray = query ? data.results : data;
        
        displayPhotos();
    } catch (error) {
        console.error('Error fetching photos:', error);
    }
}

displayPhotos()

Iterates photosArray and constructs a pair of DOM elements for each photo — an <a> anchor wrapping an <img> — then appends each pair to #Image__Container. At the start of each call it sets totalImages = photosArray.length so the batch-completion logic in imageLoaded() knows how many images to wait for. Each <img> receives a load event listener that calls imageLoaded(). Unsplash photo object fields used:
FieldUsed as
photo.links.htmlhref on the <a> element
photo.urls.regularsrc on the <img> element
photo.alt_descriptionalt and title on the <img> element
const displayPhotos = () => {
    totalImages = photosArray.length;

    photosArray.forEach(photo => {
        const item = document.createElement('a');
        setAttributes(item, {
            href: photo.links.html,
            target: '_blank',
        });

        const img = document.createElement('img');
        setAttributes(img, {
            src: photo.urls.regular,
            alt: photo.alt_description,
            title: photo.alt_description,
        });

        img.addEventListener('load', () => {
            imageLoaded();
        })

        item.appendChild(img);
        imageContainer.appendChild(item);
    });
}

setAttributes(element, attributes)

A lightweight utility that sets multiple HTML attributes on a DOM element in a single call by iterating over the keys of the attributes object. Parameters:
element
HTMLElement
required
The DOM element on which attributes will be set.
attributes
object
required
A plain object whose keys are attribute names and whose values are the corresponding attribute values. Example: { src: '...', alt: '...' }.
const setAttributes = (element, attributes) => {
    for (const key in attributes) {
        element.setAttribute(key, attributes[key]);
    }
}

imageLoaded() (async)

Called by each image’s load event listener. It increments both imagesLoaded (batch counter) and totalLoadedImages (session counter), then updates the #image-counter element’s text content. When imagesLoaded === totalImages (i.e., every image in the current batch has loaded), it:
  1. Resets imagesLoaded to 0
  2. Sets ready = true — enabling the next infinite-scroll trigger
  3. Sets loader.hidden = true — hiding the loading overlay
const imageLoaded = async () => {
    imagesLoaded++;
    totalLoadedImages++;
    imageCounter.textContent = totalLoadedImages;
    
    if (imagesLoaded === totalImages) {
        imagesLoaded = 0;
        ready = true;
        loader.hidden = true;
    }
}

handleSearch()

Invoked when the user clicks #search-button or presses Enter inside #search-input. It performs a full gallery reset before fetching new results:
  1. Reads searchInput.value and assigns it to query
  2. Clears the gallery by setting imageContainer.innerHTML = ''
  3. Resets totalLoadedImages to 0 and updates the counter display
  4. Shows the loader overlay (loader.hidden = false)
  5. Calls getPhotos() to fetch the first batch for the new query
const handleSearch = () => {
    query = searchInput.value;
    imageContainer.innerHTML = '';
    totalLoadedImages = 0;
    imageCounter.textContent = '0';
    loader.hidden = false;
    getPhotos();
};

Event Listeners

Four event listeners wire up user interactions and scroll behaviour.

searchButtonclick

searchButton.addEventListener('click', handleSearch);
Calls handleSearch() whenever the search button is clicked.

searchInputkeypress

searchInput.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') {
        handleSearch();
    }
});
Calls handleSearch() when the user presses the Enter key while the search input is focused.

windowscroll

The scroll listener handles three separate responsibilities in a single callback:
window.addEventListener('scroll', () => {
    const { innerHeight, scrollY } = window;
    const { offsetHeight } = document.body;
    if (innerHeight + scrollY >= offsetHeight - 1000 && ready) {
        ready = false;
        getPhotos();
    }

    if (scrollY > 50) {
        titleColumn.classList.add('scrolled');
    } else {
        titleColumn.classList.remove('scrolled');
    }

    if (scrollY > innerHeight) {
        scrollTopBtn.classList.add('visible');
    } else {
        scrollTopBtn.classList.remove('visible');
    }
})
ResponsibilityConditionAction
Infinite scroll triggerinnerHeight + scrollY >= offsetHeight - 1000 and ready === trueSets ready = false, then calls getPhotos() to load the next batch
Sticky header compact modescrollY > 50Adds scrolled class to .Title__Column; removes it otherwise
Scroll-to-top visibilityscrollY > innerHeightAdds visible class to #scrollTop; removes it otherwise
The infinite-scroll threshold of 1000px before the page bottom means new photos begin loading before the user reaches the very end of the feed, creating a seamless experience.

scrollTopBtnclick

scrollTopBtn.addEventListener('click', () => {
    window.scrollTo({
        top: 0,
        behavior: 'smooth'
    });
});
Smoothly scrolls the page back to the top when #scrollTop is clicked.

Initialization

At the very end of Infinity.js, getPhotos() is called once unconditionally as the script loads. This populates the gallery with an initial batch of random photos before any user interaction.
getPhotos();
Because query is '' at this point, the initial call always fetches random photos from the Unsplash random endpoint. The gallery will only switch to search mode after the user submits a search term via handleSearch().

Build docs developers (and LLMs) love