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.
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 img
s 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:
- access each image in the collection by its position in the list, using the loop counter for the position
- add an event handler to that image for the mouseover event
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:
-
get the object that triggered the event, using the
target
property of the event object. That object will be an image in the gallery.let img = e.target;
-
get the source of that image. That is the value of its
src
attribute:let imgSrc = img.getAttribute('src');
-
get the featured image, which is in a
div
with an id of "featured":let featuredImage = document.querySelector('#featured img');
-
change the value of the featured image's
src
attribute to thesrc
attribute of the image that triggered the eventfeaturedImage.setAttribute('src', imgSrc);
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:
-
access the
a
anchor element that is the image's parent in the tree, usingimg.parentNode
, then -
access the anchor's
href
attribute to get the URL for the higher quality image
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:
-
Go to the HTML file in VS Code. Scroll to the bottom and
change the
src
attribute for thescript
element to"click.js"
. - Open the click.js script file in VS Code.
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:
- the image cannot move backward, because the counter is at the beginning of the collection
- the image cannot move forward, because the counter is at the end of the collection
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:
mouseover.js
click.js
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:
- In Version 2, when the user reaches the beginning or end of the collection, make the buttons "wrap around": pressing the 'next' button when on the last image takes us to the first image, and pressing the 'previous' button when on the first image takes us to the last image.
-
In Version 1, add a randomizing behavior. If the clicks on
the featured image, it is replaced by a random image from
the gallery. Recall that you can use the
Math
object'srandom()
method to get a random number between 0 and 1. -
Add the randomizing behavior to Version 2 as well, but keep
the buttons in sync with the change: Make sure that the jump
updates the
current
variable.