Lab Exercise: An Image Gallery with User Interaction

Setup: Files

Download the starter code for the exercise. It contains a web page, a style sheet for that page, and two empty script files for your JavaScript code.

Unzip the file and put the folder in the place where you keep all of your class code. Open the web page in a browser. In VS Code, open both index.html and the script file mouseover.js. Your development environment is ready.

Setup: The Story

The web page is a simple image gallery. It displays a large 'featured image' and fifteen thumbnail images, all of sights around UNI's campus. Each thumbnail is a link to a higher-quality version of the same image. Click on one to see the larger version.

Side note: The HTML code in the gallery div is long and repetitive. Can you think how we might write JavaScript code to generate this HTML from a list of filenames?

The goal of this exercise is to let the user navigate through this gallery, displaying different featured images.

In Version 1, which you will implement in mouseover.js, you will add a basic "mouseover" effect: when the cursor is over an image in the gallery, that image is displayed as the featured image.

In Version 2, which you will implement in click.js, you will activate the buttons on either side of the featured image. The user will click a button to move through the gallery, one image at at a time.

Version 1: A Basic Mouseover Effect

Your first task is to enable a mouseover effect: when the mouse cursor is over an image in the gallery, the featured image changes to that gallery image.

Step 1: implement mouseover effect

To do this, we will need an event handler. Create an empty one now:

function changeFeaturedImage(e) {
  // your code goes here
}

Look at the HTML file: how would you select all the images in the gallery?

We can use the querySelectorAll() method, with a selector that get all of the imgs inside the #gallery div, and assign the result to a variable.

Just below the function, write this code:

let images = document.querySelectorAll('#gallery img');

Now we have all the image elements in one collection. It is a NodeList, which behaves like an array. We can loop over the collection and add our event handler function to each one, using the addEventListener() method.

A for loop will do the trick. We can start at 0 and continue up to the length of the images collection. Inside the loop we need to:

Just after the new variable, add this code:

for (let i = 0; i < images.length; i++) {
  let image = images[i];
  image.addEventListener('mouseover', changeFeaturedImage);
}

Now we can implement the event handler.

Inside the function, we need to:

Try it out! Yes, the images are blurry. We will fix that soon.

Step 2: implement mouseout effect

Let's improve the user experience delivered by our script. It would make sense for the featured image to be restored to its original state once the mouse leaves an image in the gallery. Using a mouseover to select something is not common on the web, so the current behavior might confuse your users.

To do that, the script should remember what the original image was. One way to do this is to store the featured image's initial src attribute in a variable that is outside the function.

Copy this line from inside the function and paste it on the first line of the file, before the function:

let featuredImage = document.querySelector('#featured img');

Then add this line just after that line:

let defaultSrc = featuredImage.getAttribute('src');

Now, no matter what the user does to the featured image, its original source is permanently stored in the defaultSrc variable.

Let's write a resetFeaturedImage() function that uses defaultSrc to set the featured image back to its original value.

Just after the changeFeaturedImage() function, put this code:

function resetFeaturedImage(e) {
  featuredImage.setAttribute('src', defaultSrc);
}

We can add this function as an event handler for the mouseout event. Add a line to the loop at the bottom of the script:

let images = document.querySelectorAll('#gallery img');
for (let i = 0; i < images.length; i++) {
  let image = images[i];
  image.addEventListener('mouseover', changeFeaturedImage);
  image.addEventListener('mouseout', resetFeaturedImage);
}

Finally, we can also improve the changeFeaturedImage() function. Now that we have stored the featured image in a variable, this function doesn't need to look for it in the DOM tree every time.

Delete that line from the function, leaving this:

function changeFeaturedImage(e) {
  let img = e.target;
  let imgSrc = img.getAttribute('src');
  featuredImage.setAttribute('src', href);
}

The script now handles mouseover and mouseout events.

Step 3: fix image quality

It is time to fix the issue with image quality. Right now, the code displays the source of the thumbnail image as the featured image in response to a mouseover event. The thumbnail is a tiny image, so when it's displayed as a larger image, it is pixelated.

Instead, let's grab the higher-quality, full-size image that the thumbnail links to. We can get that URL from the anchor tag that is the image's parent. For that, we will use another feature of the DOM!

We need modify only the changeFeaturedImage() function. Instead of using the thumbnail image as a source for the image file, we:

Change the function to:

function changeFeaturedImage(e) {
  let img = e.target;
  let anchor = img.parentNode;
  let href = anchor.getAttribute('href');
  featuredImage.setAttribute('src', href);
}

The script now handles mouseover and mouseout events and displays the high-quality versions of all the images. The resulting script delivers an attractive dynamic web page!

Version 2: Basic Button Click Behavior

In our second version, let's activate the buttons on the page. Each time the user clicks one of the buttons, the featured image advances to the next image in the gallery, or the previous one.

Before we start, let's set up our files again:

Your development environment is ready.

Step 1: re-implement the common code

With such similar behavior as the previous version, we will need some of the same code. Type this code now, or copy it from mouseover.js and paste it into click.js:

let images = document.querySelectorAll("#gallery img");
let featuredImage = document.querySelector("#featured img");

function changeFeaturedImage(e) {
  let img = e.target;
  let anchor = img.parentNode;
  let href = anchor.getAttribute('href');
  featuredImage.setAttribute('src', href);
}

Step 2: set up event handlers for the buttons

Both buttons will change the featured image, but in a different way.

Just below the changeFeaturedImage() function, write this code to add handlers to the buttons:

let previousButton = document.querySelector("#prev");
previousButton.addEventListener('click', gotoPrevious);

let nextButton = document.querySelector('#next');
nextButton.addEventListener('click', gotoNext);

Now, between the changeFeaturedImage() function and those statements, define initial versions of the event handlers:

function gotoPrevious(e) {
  changeFeaturedImage(e);
}

function gotoNext (e) {
  changeFeaturedImage(e);
}

Step 3: modify changeFeaturedImage() with new behavior

The code we need for this function is almost exactly the same as in our previous version, with one exception. Instead of getting the source of the next image from the event, it will get the next image in line, either before or after the current image.

This means that our function needs to know the position of the image that is currently being featured.

At the top of the file with the other variables, create and initialize a new variable: for the position:

let current = 0;

Now we can modify our functions to use the variable.

First, change the first line of changeFeaturedImage() to get an image based on its position:

function changeFeaturedImage(e) {
  let img = images[current];
  let anchor = img.parentNode;
  let href = anchor.getAttribute('href');
  featuredImage.setAttribute('src', href);
}

Next, modify gotoPrevious() and gotoNext() to change the position of current before changing the image:

function gotoPrevious(e) {
  current--;
  changeFeaturedImage(e);
}

function gotoNext (e) {
  current++;
  changeFeaturedImage(e);
}

Now, when the user clicks the next button, the counter will be incremented by one, and when they click the previous button, the counter will be decremented by one. Try it out!

Step 4: guard the ends of the list

One last step: we need to guard against cases when:

Why is that a problem? If the user clicks on the back button when at the beginning of the list of images, the button handler subtracts one from the value of i even though it can't change the image. But then the user has to press the forward button extra times to get the value of i back to 0. Try it.

How do we fix this? A couple of if statements will do the job:

function gotoPrevious(e) {
  if (current > 0) {
    current--;
    changeFeaturedImage(e);
  }
}

function gotoNext (e) {
  if (current < images.length-1) {
    current++;
    changeFeaturedImage(e);
  }
}

And we are done! The script now handles click events on the buttons and displays the high-quality versions of all the images. The resulting script again delivers an attractive dynamic web page.

Submit Your Work

Submit these files:

using the course submission system, under the entry Session 23 Exercise.

Bonus Tasks

If you enjoy this exercise or want more practice, here are a few more things you can add to the gallery: