This app displays random pictures from unsplash. It implements infinite scroll, which loads more pictures as the user scrolls down. The pictures are fetched from the Unsplash API. In the title there is a title where the user can write a query word to get pictures related to their query. In these examples we see the results for 'mountain' and 'fox'. By default, it loads pictures related to nature.
It's also mobile responsive.
The HTML will have three parts:
-
The title
-
The loader that will have a
hidden
attribute when the pictures are loaded.<div class="loader" id="loader" hidden> <img src="loader.svg" alt="loader" /> </div>
-
The picture container where new
<img>
will be created.<div class="image-container" id="image-container"> <img src="sourceUrl" alt="description"> // ... </div>
- Creating a container for the images
- Using media queries to make it look good on mobile
- Using columns property to display multiple pictures in different columns.
👉🏻 Using Unsplash API
Location: https://api.unsplash.com/photos/?client_id=YOUR_ACCESS_KEY
👉🏻 we need API KEY that we pass as a query when requesting. I'll also be making use of the query
parameter to allow users filter images by topic.
Count: In order to increase performance, the first fetch will only get 5 images for mobile devices and 15 for other screens (in order to make the column display look good). Whenever the first pack of pictures have been loaded, the count will then be the max of 30.
// Unsplash API
// In phone first load is 5 pics, otherwise 15
const initialCount = window.innerWidth > 500 ? 15 : 5;
const regularCount = 30;
let isFirstLoad = true;
Async function getPhotos()
// Using default parameters so that if the user doesn't write anything in the input, nature pics are displayed
async function getPhotos(query = 'nature') {
let count;
// If it's first time load, count will be initialCount (5 / 15), otherwise regularCount (30)
if (isFirstLoad) {
count = initialCount;
} else {
count = regularCount;
}
const apiUrl = `https://api.unsplash.com/photos/random/?client_id=${apiKey}&count=${count}&query=${query}`;
try {
const response = await fetch(apiUrl);
photosArr = await response.json(); // we save info in global array photosArr
displayPhotos();
} catch (error) {
console.log(error);
}
}
We save the information into a global array, then with forEach
we create the html elements for each element. Because we are setting many attributes, we create a helper function to take care of this task:
// Helper Function to Set Attributes on DOM Elements
function setAttributes(element, attributes) {
for (const key in attributes) {
element.setAttribute(key, attributes[key])
}
}
photosArr
is indeed a nodeList and not an array, but forEach
is now also supported for array-like structures.
const elements = {
imageContainer: document.getElementById('image-container'),
loader: document.getElementById('loader'),
}
let photosArr = [];
function displayPhotos() {
// Reset number of images loaded
imagesLoaded = 0;
// Update Total Nº of Images
totalImages = photosArr.length;
photosArr.forEach((photo) => {
// Create <a> that links to Unsplash
const item = document.createElement('a');
setAttributes(item, {
href: photo.links.html,
target: '_blank',
});
// Create <img> for photo
const img = document.createElement('img');
setAttributes(img, {
src: photo.urls.regular,
alt: photo.alt_description || 'unknown', // avoid "null" when no description
title: photo.alt_description || 'unknown',
});
// Put <img> inside of <a> and place them inside imageContainer
item.appendChild(img);
elements.imageContainer.appendChild(item);
// Image was loaded
imageLoaded(); // This is for the next step: checking if each image was loaded so that we can implament infinite scrolling
});
}
As we scroll down, before we reach the end, we want our function getPhotos
to run. For this purpose, we can use the scroll
event (when an element's scrollbar is being scrolled) (ref)
We need to check if the scrolling is near bottom of page and then load more photos getPhotos
function once.
Ways of implementing infinite scroll functionality:
window.innerHeight
: the total height of the browser windowwindow.scrollY
: Distance from top of page user has scrolled (it goes up as user scrolls down)document.body.offsetHeight
: height of everything in the body, including what is not within the view
We use the offsetHeight
to subtract the amount of px that we want to load the pics.
window.addEventListener('scroll', () => {
if (document.innerHeight + window.scrollY >= document.body.offsetHeight - 1000) {
getPhotos();
}
})
We need to create a ready
boolean that will only be true once the images have finished loading and only then the other statement must be evaluated. For this purpose, we'll use the load
event (ref)
// Check if all images were loaded
function imageLoaded() {
imagesLoaded++;
if (imagesLoaded === totalImages) {
isFirstLoad = false; // this will later update our photos count
ready = true;
// Hide the Loader
elements.loader.hidden = true;
}
}
// check when each is finished loading
img.addEventListener('load', imageLoaded)
Now we only want to scroll event listener run offloaded is equal to true (and then set to false)
Logo created with https://loading.io/