In the world of culinary delights, a picture is truly worth a thousand words. At Fooderia, they understand the profound impact that visually appealing images can have on their users' cooking and browsing experience. Therefore, they are excited to introduce the Image Gallery, a curated collection of high-quality photos that vividly showcase the beauty and diversity of the recipes. This gallery is not just a feast for the eyes but a source of inspiration, enabling users to visualize the dishes they'll be crafting in their kitchens.
After thorough discussions and meticulous planning, the team at Fooderia reached a pivotal decision to implement a fixed aspect ratio across all images in the Image Gallery feature. This decision was underpinned by multiple factors that directly influence user experience and website aesthetics:
Consistency: A uniform aspect ratio ensures a cohesive and appealing presentation
Responsiveness: Enhances and simplifies the layout's responsiveness
Content Management: Streamlines the content management process
Performance: Optimizes image loading and rendering performance
Tutorial
Oh, images, of course they will have images. You get right on it and start with the strategy you will employ on this journey. You think of the ways you will seperate the different aspects of showing images a kind of slider. A component for showing images is a must, and a way to get the list of images to be shown, and someway to showcase and test the component.
Unfortunatly, the team at Fooderia has not yet decided on what image provider to use, so you plan on starting out with getting images from the free image provider picsum.photos. Even though Next.js and their Image component has powerful features for working with images, you decide on getting fixed sized imaged of 600x400 pixels for starters, so you can focus on the layout and the gallery itself at this early stage. And as always you are determined in making the browser do most of the heavy lifting in rendering the gallery.
You finally come up with a basic infrastructure:
app
components
ImageGallery
Demo.jsx
ImageGallery.jsx
index.js
use-images.js
And the content of the files:
components/ImageGallery/use-images.js
import { useState } from 'react'
// The plan is to create a hook that fetches n number of images
// from the Picsum API with the specified height and width.
After verifying that the new components are displayed in the browser, it is time to get to work. You start with the hook so that you have something to populate the image gallery with. Examining the picsum site for awhile you find a suitable endpoint to use : https://picsum.photos/v2/list, where you get a json response in the form of a list of objects with some useful properties. Go ahead and finish the custom hook you have started, ane make use of the picsum.photo api:
After a few tries you finally reach the result you were expecting, a printout of an array of ten image objects. You notice that the width, height and url properties are correct.
Before starting to get all nitty gritty on the ImageGallery component you feel like you need to make sure that you can display the images in the first place. You know about the nextjs Image object and use that to render the list of images in the component:
components/ImageGallery/ImageGallery.jsx
import Image from 'next/image'
export const WIDTH = 600
export const HEIGHT = 400
export default function ImageGallery ({ images = [] }) {
return (
// Render the images horizontally in a flex container
<div className="flex">
{/* Do not render anything if there are no images */}
Wot? You expected the images to show up stacked nicely across your screen, instead the whole freaking app crashed with some error message:
Sigh, you learn more about the error message by following the link in the error message. Hm, as it turns out, in Nextjs you have to be very explicit about where the application is allowed to load images from. This is for security reasons. Well, well, that is a good thing, so you go ahead and configure your app: open the next.config.js file and add the magic config
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [{
protocol: 'https',
hostname: 'picsum.photos',
port: '',
}],
},
};
export default nextConfig;
After a refresh of the browser tab, you are able to see ten beautiful images lined up horizontally on the screen. You get excited, if only you could try to create like a view box on top of the images that is the size of one image and render the images inside it you would be almost done right? It is worth a shot:
components/ImageGallery/ImageGallery.jsx
...
export default function ImageGallery ({ images = [] }) {
return (
// This is the "view-box". Since images are fetched with a
// fixed width and height, set the max width to not be wider
// than the image width.
// The overflow is set to auto to make it scrollable, this will
// hide the "overflow".
// The 'snap-x' and 'snap-mandatory' classes are used to make the
// container content "snappable" in order to give it the
// carousel feel when scrolling.
// The 'aspect-[3/2]' class is used to set the aspect ratio of
fill // <- Replace 'height' and 'width' with 'fill'
/>
</div>
))}
</div>
)
}
Wow, you might be home by dinner tonight! Now you need to get rid of that ugly scroll-bar (you notice it when you use a mouse pointer device), and add previous/next buttons to be able to navigate the gallery better. Tailwind CSS does not have any good class to hide scrollbars, so you must add it yourself. There is a globals.css file in the app folder that works as the entry point to Tailwind, open it and add the hide scrollbar magic:
app/globals.css
/* ... */
@layer utilities {
.text-balance {
text-wrap: balance;
}
/* Add this utility: */
.scrollbar-none::-webkit-scrollbar {
display: none;
}
.scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
}
}
Now you can hide the scrollbar and add previous/next buttons to the gallery:
components/ImageGallery/ImageGallery.jsx
...
// Represents the direction of the navigation, mostly for
// readability...
const NEXT = true
const PREV = false
export default function ImageGallery ({ images = [] }) {
// A ref to be used to get a referrence to the scroll container
// element, do not forget to import useRef from react at the top.
const ref = useRef ()
// The function that will let us navigate to the next or previou
// image
const goTo = (isNext = false) => {
// How much to scroll is determined by the actual width of the
// container, and based on the direction we want to go.
Oh, okay, it works at least. You figure it is time for an assessment of the component as it stands now: The previous and next buttons are now rendered on every image, that looks and feels a bit strange. You decide you have to add an extra 'layer' for the controls, especially since you are planning on adding more controls to the gallery. Also, when displaying the first and last image it is odd that there is a previous/next button there that does not do anything, you recon that just has to be fixed. You realise that in order to do that you will have to keep track of what image is currently in display, you need an index.
OMG, you are thinking of calling it a day, but a final touch beckons to truly round off the project: implementing a classic "dot"-navigation at the bottom of the component. This elegant solution not only adds a subtle yet sophisticated aesthetic touch but also significantly enhances user navigation. A dot-navigation system offers an intuitive, visually minimalistic way for users to understand their position within the gallery and effortlessly move between images. By seamlessly integrating this feature, the aim is to provide an even more polished and user-friendly interface, ensuring that Fooderia's website visitors can smoothly browse through the culinary delights on offer, thereby elevating their overall experience with the website to new heights of interactive elegance. It's the perfect finishing touch to complement the robust functionality and appealing design of the Image Gallery component.
To accomplish this you need to add the dots to the component at the same levels as the previous/next buttons, and add a function to handle dot navigation:
components/ImageGallery/ImageGallery.jsx
import { useRef, useState } from 'react'
import Image from 'next/image'
export const WIDTH = 600
export const HEIGHT = 400
const NEXT = true
const PREV = false
export default function ImageGallery ({ images = [] }) {
{/* Create an array of ●:s with the same length as the number
of images. Render them with a different color depending on
the current index. Call the handler when clicked. */}
{Array(images.length)
.fill('●')
.map((dot, i) => (
<button
className={`mx-0.5 ${index === i ? "text-slate-200" : "text-slate-700"}`}
onClick={() => instantScrollTo(i)}
key={i}
>
{dot}
</button>
))}
</div>
</div>
)
}
Finally, with a grin of satisfaction, you commit and push the latest updates. The anticipation of the Foodorians' reaction to this masterpiece adds an extra spark to your smile. Amidst the thoughts of what tonight's dinner will entail, you sneak a quick glance at the task board, curious about tomorrow’s adventures.