A slideshow gallery is part of the visual display modes you find on the web. It helps users navigate between images by boldly showing one picture at a time, leaving the other ones available on the side.
This blog post shows you how you can build a full-viewport slideshow gallery.
PREREQUISITES
- Basic knowledge of JavaScript, React and styled-components
The complete code is in this repo.
Layout of a slideshow gallery
What will be the structure of our slideshow? I got us covered with the following wireframe:
The Slide Wrapper
From our wireframe, we see that a container wraps all the elements. So first, let’s create a SlideWrapper
styled component:
// src/slideshow-gallery/index.js
import styled from 'styled-components';
const View = () => <Slideshow />;
const Slideshow = () => {
return <SlideWrapper></SlideWrapper>;
};
const SlideWrapper = styled.div`
position: relative;
width: 100vw;
height: 100vh;
`;
export default View;
The SlideWrapper
occupies the entire viewport’s width and height. We wanted a full-viewport slideshow, right? And note that the children will position themselves relative to this wrapper, hence the position: relative;
.
The Image Box
Each selected image will be in a box that preserves the image ratio (width/height). So let’s create a wrapper around an <img>
tag called ImageBox
. This wrapper will put the image into a constraint. That is, the image must stay within the delimitation of the wrapper. That way, our slideshow will remain stable no matter the image size and orientation.
In the following, we define and use the ImageBox
component:
// src/slideshow-gallery/index.js
// ...
const ImageBox = styled.div`
position: relative;
background-color: #343434;
width: 100%;
height: 85%;
img {
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
max-width: 100%;
max-height: 100%;
}
`;
const Slideshow = () => {
return (
<SlideWrapper>
<ImageBox>
<img alt="" src="/pathToAnImage" />
</ImageBox>
</SlideWrapper>
);
};
//...
Here is the result with different image orientations and sizes:
Our ImageBox
needs a left and right button to help us switch between images. So let’s create a NavButton
styled component to accomplish that:
// src/slideshow-gallery/index.js
import styled, { css } from 'styled-components';
import rurikoTempleImage from './assets/ruriko-in-temple.jpeg';
import { ReactComponent as ChevronLeft } from './assets/chevron-left.svg';
import { ReactComponent as ChevronRight } from './assets/chevron-right.svg';
// ...
const Slideshow = () => {
return (
// ...
<ImageBox>
<img alt="" src={rurikoTempleImage} />
<NavButton position="left">
<ChevronLeft />
</NavButton>
<NavButton position="right">
<ChevronRight />
</NavButton>
</ImageBox>
// ...
);
};
const NavButton = styled.button`
cursor: pointer;
position: absolute;
top: 45%;
padding: 5px;
border-radius: 3px;
border: none;
background: rgba(255, 255, 255, 0.7);
${({ position }) =>
position === 'left' &&
css`
left: 10px;
`}
${({ position }) =>
position === 'right' &&
css`
right: 10px;
`}
`;
// ...
The NavButton
is vertically centered in the ImageBox
(top: 45%;
). Based on the position prop, the NavButton
is either positioned to the left or the right.
It would also be nice to have a caption at the bottom:
// src/slideshow-gallery/index.js
const Slideshow = () => {
return (
<SlideWrapper>
<ImageBox>
// ...
<ImageCaption>Ruriko Temple</ImageCaption>
</ImageBox>
</SlideWrapper>
);
};
// ...
const ImageCaption = styled.span`
width: 100%;
text-align: center;
font-weight: bold;
position: absolute;
bottom: 0;
padding: 8px;
background: rgba(255, 255, 255, 0.7);
`;
// ...
And we get the following:
Getting the Slideshow Items as prop
The slideshow needs to get a set of images from the outside. The src/slideshow-gallery/data.js
file export an array of pictures that we can use. Each item gives access to the image source as well as the image caption:
// src/slideshow-gallery/data.js
import rurikoTemple from './assets/ruriko-in-temple.jpeg';
import itsukushimaShrine from './assets/itsukushima-shrine.jpeg';
// ...
const slideItems = [
{
image: nemichiJinja,
caption: 'Nemichi-Jinja, Seki'
},
{
image: itsukushimaShrine,
caption: 'Itsukushima Shrine'
}
// ...
];
export default slideItems;
Let’s import this array and pass it down to the Slideshow
component:
// src/slideshow-gallery/index.js
// ...
import data from './data';
const View = () => <Slideshow items={data} />;
// ...
As our Slideshow
component will render differently based on the selected image, we need to use a state. This state will hold all of the slide items in addition to the index of the currently active item:
// src/slideshow-gallery/index.js
import { useState } from 'react';
// ...
const Slideshow = (props) => {
const [{ items, activeIndex }, setState] = useState({
items: props.items,
activeIndex: 0 // begin with the first item
});
return (
<SlideWrapper>
<ImageBox>
<img alt={items[activeIndex].caption} src={items[activeIndex].image} />
<NavButton position="left">
<ChevronLeft />
</NavButton>
<NavButton position="right">
<ChevronRight />
</NavButton>
<ImageCaption>{items[activeIndex].caption}</ImageCaption>
</ImageBox>
</SlideWrapper>
);
};
// ...
Navigating between Images
With the state in place, we can add a click handler function to each NavButton
to change the image:
// src/slideshow-gallery/index.js
// ...
const Slideshow = (props) => {
// ...
const moveTo = (newIndex) => () => {
if (newIndex === -1) {
// jump from the first image to the last
setState((s) => ({ ...s, activeIndex: items.length - 1 }));
return;
}
if (newIndex === items.length) {
// jump from the last image to the first
setState((s) => ({ ...s, activeIndex: 0 }));
return;
}
setState((s) => ({ ...s, activeIndex: newIndex }));
};
return (
<SlideWraper>
// ...
<NavButton position="left" onClick={moveTo(activeIndex - 1)}>
// ...
<NavButton position="right" onClick={moveTo(activeIndex + 1)}>
// ...
</SlideWraper>
);
};
// ...
Thumbnail Images
After the ImageBox
, we want a list of thumbnails for all of our images. That list will show the active image thumbnail with 100% opacity. And the inactive ones will be 40% transparent.
// src/slideshow-gallery/index.js
// ...
const Slideshow = (props) => {
// ...
return (
<SlideWraper>
// ...
</ImageBox>
<ThumbnailList>
{items.map((item, index) => (
<Thumbnail active={activeIndex === index} src={item.image} />
))}
</ThumbnailList>
</SlideWraper>
);
};
const ThumbnailList = styled.div`
display: flex;
align-items: stretch;
width: 100%;
height: 15%;
`;
const Thumbnail = styled.div`
cursor: pointer;
opacity: ${({ active }) => (active ? 1 : 0.6)};
background-image: url(${({ src }) => src});
background-size: cover;
background-position: center;
flex-grow: 1;
:hover {
opacity: 1;
}
`;
// ...
Finally, we want to jump directly to an image by clicking on its thumbnail. To do that, we reuse our moveTo
function:
// src/slideshow-gallery/index.js
// ...
{
items.map((item, index) => (
<Thumbnail
onClick={moveTo(index)}
// ...
/>
));
}
// ...
And now, the slideshow gallery is ready! Take a look at the final result:
Conclusion
From a wireframe, we broke down the distinct parts of the slideshow. It was the cornerstone we built upon until the final UI.
You can pat yourself on the back for making it to the end.
Thanks for reading!