In a previous tutorial, we learned how to build a responsive image gallery with slick.js. Today, let’s build something similar: a responsive image gallery with a draggable featured image/main slide. To make the target element draggable, we’ll take advantage of GSAP’s Draggable plugin.
Sounds like a good exercise?
What We’re Building
Here’s the gallery that we’re going to create:
1. Include the Required Plugins
As already discussed, to make the featured images draggable elements, we’re going to use GSAP and specifically its Draggable plugin.
Optionally, we’ll also include InertiaPlugin (formerly ThrowPropsPlugin), a second GSAP plugin that will apply a momentum-based movement after the mouse/touch is released. It’s worth noting that this is a premium plugin, and you have to sign up for a GSAP membership before deciding to use it. In our case, we’re going to use a trial version that only works locally and on domains like codepen.io (see the browser console of the demo for more details).
With all these in mind, we’ll include three external JavaScript files. The first two are mandatory, while the third one is optional.
2. Define the HTML Markup
We’ll define a wrapper element that will contain two lists: the list of thumbnail images and the list of featured images. Both lists will include the same Unsplash images. These will have equal dimensions and be big enough to implement the draggable effect.
By default, the first main slide will appear. But we can configure that behavior by attaching the is-active
class to the desired slide (lists).
In addition, all featured images will retain their original dimensions (1920 x 1280px).
Here’s the required structure for our demo page:
<div class="gallery-wrapper"> <ul class="thumb-list"> <li class="is-active"> <img width="1920" height="1280" src="sports-car1.jpg" alt=""> </li> <li> <img width="1920" height="1280" src="sports-car2.jpg" alt=""> </li> <li> <img width="1920" height="1280" src="sports-car3.jpg" alt=""> </li> <li> <img width="1920" height="1280" src="sports-car4.jpg" alt=""> </li> </ul> <ul class="featured-list"> <li class="is-active"> <div class="featured-img" style="background-image: url(sports-car1.jpg); width: 1920px; height: 1280px;"></div> </li> <li> <div class="featured-img" style="background-image: url(sports-car2.jpg); width: 1920px; height: 1280px;"></div> </li> <li> <div class="featured-img" style="background-image: url(sports-car3.jpg); width: 1920px; height: 1280px;"></div> </li> <li> <div class="featured-img" style="background-image: url(sports-car4.jpg); width: 1920px; height: 1280px;"></div> </li> </ul> </div>
3. Specify the Main Styles
With the markup ready, we’ll continue with the main styles of our gallery. For simplicity, I’ll skip the introductory/reset ones. Also, I won’t optimize or merge the common CSS styles, so it will be easier for you to understand what is going on. Be sure to see all of them by clicking the CSS tab of the demo.
Set Gallery Layout
The gallery will have a maximum width of 950px.
On large screens (>750px), we’ll have two columns. The thumbnails will appear on the left side, while the featured images will be on the right, like this:
Notice that the thumbnails will cover a quarter of the gallery width, while the featured images will cover three quarters.
On small screens (≤750px), the thumbnails will sit underneath the featured image, like this:
Notice that each thumbnail will cover one quarter of the parent’s width.
Here are the associated styles:
.gallery-wrapper { max-width: 950px; padding: 0 15px; margin: 0 auto; display: grid; grid-template-columns: 1fr 3fr; grid-gap: 15px; } .gallery-wrapper .thumb-list { display: grid; grid-gap: 15px; } @media (max-width: 750px) { .gallery-wrapper { grid-template-columns: 1fr; } .gallery-wrapper .thumb-list { grid-template-columns: repeat(4, 1fr); order: 1; } }
Featured Slides Visibility
By default, all featured slides will be hidden, apart from the active slide. Plus, only one featured slide (the active one) will appear at a time.
Here are the associated styles:
.gallery-wrapper .featured-list li { opacity: 0; transition: opacity 0.25s; } .gallery-wrapper .featured-list li.is-active { opacity: 1; }
Position Featured Images
On large screens, both gallery columns will have the same height as they are grid items. The featured images though will be absolutely positioned elements and centered within their container. To view all their parts we have to drag over them.
On small screens, as the columns are stacked and the featured images are still absolutely positioned, we should specify a fixed height for the right column.
Here are the associated styles:
.gallery-wrapper .featured-list { position: relative; overflow: hidden; } .gallery-wrapper .featured-list .featured-img { background-size: cover; background-repeat: no-repeat; background-position: center; z-index: 1 !important; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } @media (max-width: 750px) { .gallery-wrapper .featured-list { height: 340px; } }
Indicate Active and Hovered States
Each time we hover over a thumbnail, its ::before
pseudo-element will appear. This will have a light blue background and sit on top of the thumbnail.
On the other hand, the active thumbnail will receive a red border color.
Here are the associated styles:
/*CUSTOM VARIABLES HERE*/ .gallery-wrapper .thumb-list li { position: relative; cursor: pointer; border: 4px solid var(--black); } .gallery-wrapper .thumb-list li:not(.is-active):hover::before { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: var(--hovered-thumb); } .gallery-wrapper .thumb-list li.is-active { border-color: var(--red); }
4. Add the JavaScript
Let’s now give life to our gallery!
Change Slides
Each time we click on a thumbnail, we’ll perform the following actions:
- Remove the
is-active
class from the pre-existing active thumbnail and featured image. - Find the index of the current active thumbnail.
- Assign the
is-active
class to the active thumbnail and the featured image whose index matches the index of this thumbnail.
Here’s the required code:
const thumbList = document.querySelector(".thumb-list"); const thumbItems = thumbList.querySelectorAll("li"); const featuredList = document.querySelector(".featured-list"); const isActiveClass = "is-active"; thumbItems.forEach(function (el) { el.addEventListener("click", function () { thumbList.querySelector("li.is-active").classList.remove(isActiveClass); featuredList.querySelector("li.is-active").classList.remove(isActiveClass); let index = Array.from(thumbItems).indexOf(el); el.classList.add(isActiveClass); featuredList .querySelector(`li:nth-child(${++index})`) .classList.add(isActiveClass); }); });
Add Keyboard Support
Let’s now enhance the user experience by providing support for keyboard navigation. More specifically:
- Each time the up (↑) or down (↓) arrow keys are pressed, we’ll retrieve the pre-existing active thumbnail.
- If the up arrow key is pressed, the thumbnail that comes before the current thumbnail will become active. In case there isn’t any such thumbnail, the last thumbnail will become active.
- If the down arrow key is pressed, the thumbnail that comes after the current thumbnail will become active. In case there isn’t any such thumbnail, the first thumbnail will become active.
Here’s the required code:
... document.addEventListener("keyup", (e) => { if (e.keyCode === 38 || e.keyCode === 40) { const activeThumb = thumbList.querySelector("li.is-active"); // up arrow if (e.keyCode === 38) { if (activeThumb.previousElementSibling) { activeThumb.previousElementSibling.click(); } else { thumbList.lastElementChild.click(); } } else { // down arrow if (activeThumb.nextElementSibling) { activeThumb.nextElementSibling.click(); } else { thumbList.firstElementChild.click(); } } } });
Make Feature Images Draggable
At this last step, we’ll make the featured images draggable elements. To do this, we’ll use the create()
method that will receive the two following arguments:
- The elements that we want to drag.
- A configuration object. Inside it, we’ll specify the bounds at which the draggable elements should stay during the effect. Optionally, as we’ve loaded the InertiaPlugin, we’ll also request though the
inertia
property momentum-based motion after users’ mouse/touch is released.
Here’s the corresponding code:
const featuredList = document.querySelector(".featured-list"); const featuredImgs = featuredList.querySelectorAll(".featured-img"); Draggable.create(featuredImgs, { bounds: featuredList, inertia: true });
Of course, here, we covered just the basic part of the plugin’s functionality. You can go even deeper by reading the docs and implementing complex stuff.
Conclusion
Another exercise has come to an end, folks! Thanks for following along. Hopefully, you enjoyed what we built today, and it gave you solid knowledge of how to combine some custom code with the power of popular plugins like GSAP.
Here’s a reminder of what we built:
Last but not least, remember that GSAP isn’t the only way to create a draggable effect. You’re more than welcome to try another option and share it with us.
In addition, if you want to practice with this demo, I have a challenge for you: create a custom lightbox that will open each time you click on the corresponding button. See the call-to-action button below:
Should you accept the challenge, you might use the modal we built some time ago as a starting point. In an upcoming tutorial, I’ll provide a possible solution. Stay tuned!
As always, thanks a lot for reading!
No comments:
Post a Comment