Build an image gallery with HTML, CSS, and JavaScript

cover

Subscribe to receive the free weekly article

CSS grid is awesome, we can't say it enough. It helps a lot on making our website responsive. Sometimes we even don't need media queries to make our layout fully responsive. In this post, I will lead you on how to build a responsive image gallery with CSS grid (only) and some JavaSript (to make it reactive), and the images will be fetched from the unsplash API. So you'll need to create an account here to get your API key.

HTML

The HTML is relatively simple as you can see.

<main>
  <div class="image--container"></div>
  <div class="image--selection"></div>
</main>

We have two div elements, the first with image--container class will be the viewer and the second div tag will enable us to select an image from the gallery. And by the way img tags will be created by JavaScript.

CSS

For the CSS, we will first make some resets to remove spaces and import Open Sans from google fonts.

@import url("https://fonts.googleapis.com/css?family=Open+Sans:700&display=swap");
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: #444;
}

main {
  padding: 0 0.5rem;
  height: 100vh;
}

Then, change the background-color for the body tag and set the height of the main tag to fit the viewport height.

.image--gallery {
  width: 100%;
  height: 32rem;
  display: block;
  margin: auto;
  border: 5px solid #222;
  object-fit: cover;
}

.image--selection {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  justify-content: center;
  align-items: center;
}

.image__item {
  width: 100%;
  height: 8rem;
  display: block;
  margin: auto;
  margin-bottom: 0.5rem;
  border: 5px solid #222;
  object-fit: cover;
  cursor: pointer;
}

To make the image viewer looking nice, we have to use .image--gallery to expand the width to 100% to have a responsive image and the most important set the object-fit property to cover. It will just ensure that the image fit its container element.

Then, for the image gallery, we use .image--selection to have a grid system. After displaying it in grid, we have to set grid-template-columns to repeat(auto-fit, minmax(9rem, 1fr)).
The repeat() function takes two arguments: the number of times to repeat the value and the value itself. The auto-fit value allows us to wrap our columns into rows when we lack spaces to display every elements in the same column in our viewport. And the minmax() function will set the minimum size of the element to 9rem and the maximum size to 1fr.

justify-content and align-items help us centering the grid elements.
Finally, the .image__item class serves to resize each image in the gallery.
rem is a relative unit of the font size(16px).
fr is just a fractional unit.

.loader {
  text-align: center;
  margin-top: 20%;
  color: #fff;
  font-family: "Open Sans", sans-serif;
  font-weight: 700;
}

.animate-entrance {
  animation: BounceIn 0.8s;
}

@keyframes BounceIn {
  0% {
    transform: scale(0.8);
    opacity: 0;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

As you can see here, we use .loader class to style the alignment, font, margin and color of the text to display if we have no images.
Then, .animate-entrance class helps us making some animations on the image viewer. For that, we use the animation property and the BounceIn value defined below with @keyframes. It will just bounce in with a fade in effect and scale a little bit. And the animation will last 0.8 seconds.

JavaScript

We start the javaScript part by selecting some elements.

const image_gallery = document.querySelector(".image--container")
const image_container = document.querySelector(".image--selection")
const loading = '<h1 class="loader">Loading...</h1>'

The first image_gallery is the viewer element, and the second image_container is the image gallery. Both elements are selected by their class names with the querySelector method. The last one loading will be used if we have no images to show.

const showImages = () => {
  // Show loading text if no data
  if (image_container.children.length === 0) image_container.innerHTML = loading
  fetch(`https://api.unsplash.com/photos?client_id=${apiKey}`)
    .then(res => {
      res.json().then(images => {
        // Call the function to create the gallery
        createImageGallery(images)
      })
    })
    .catch(e => {
      console.log(e)
    })
}

The showImages() function will display images fetched from the unsplash API. First, we need to check if the image_container have a child element or not, in other words if the image gallery is created in the DOM or not and display Loading... while fetching data from unsplash with the fetch API. Then, if we get images pass it to the createImageGallery() function as an argument to display images otherwise catch the error.

For the apiKey in the url, you need to create an account here and replace it with your API key.

const createImageGallery = images => {
  let output = ""
  // Show the first image on the viewer
  image_gallery.innerHTML = `<img src="${images[0].urls.regular}" class="animate-entrance image--gallery" alt="${images[0].alt_description}">`
  setTimeout(() => {
    image_gallery.children[0].classList.remove("animate-entrance")
  }, 500)
  // show unselected images
  images.forEach(({ urls, alt_description }) => {
    output += `<img src="${urls.regular}" alt="${alt_description}" class="image__item" />`
  })
  image_container.innerHTML = output
}

To create the viewer in the DOM, we use the property innerHTML to append the element to image_gallery. Then, we remove animate-entrance class after 0.5 seconds to be able to animate the viewer again.

And we loop through images(data fetched from unsplash) and just pull out urls and alt_description to create the img tag and append it to image_container to create the image gallery's selection on the DOM.

const changeImage = e => {
  // Get the viewer image element
  const image = image_gallery.children[0]
  if (e.target.src) {
    // change the image
    image.classList.add("animate-entrance")
    image.src = e.target.src
    setTimeout(() => {
      image.classList.remove("animate-entrance")
    }, 800)
  }
}

// Event listeners
document.addEventListener("DOMContentLoaded", showImages)
image_container.addEventListener("click", changeImage)

To change the image on the viewer, we need to get the child element(img tag) of image_gallery. Then, check if the event provided as an argument contains a src attribute of not. And then change the viewer image with the selected one and add the .animate-entrance class to the img tag. Then, we remove animate-entrance class after 0.5 seconds to be able to animate the viewer again.

And to make it happen, we need to listen to two events. The first DOMContentLoaded will fire the showImages() function to display all images when the initial HTML document will be completely loaded. The second listens for click event on the image_container element and change the viewer image with the function changeImage().

You can check it live here

stepper-form

That all folks!!!