DEV Community

Cover image for How to Make a Slideshow Gallery with ReactJS and Styled-Components
Nazif Barassounon
Nazif Barassounon

Posted on • Originally published at nazifbara.com

How to Make a Slideshow Gallery with ReactJS and Styled-Components

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:

slideshow 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;
Enter fullscreen mode Exit fullscreen mode

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.

image box concept

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>
  );
};
//...
Enter fullscreen mode Exit fullscreen mode

Here is the result with different image orientations and sizes:

image box result

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;
    `}
`;

// ...
Enter fullscreen mode Exit fullscreen mode

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.

Nav Buttons

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);
`;

// ...
Enter fullscreen mode Exit fullscreen mode

And we get the following:

Image caption

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;
Enter fullscreen mode Exit fullscreen mode

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} />;
// ...
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
// ...
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
// ...
Enter fullscreen mode Exit fullscreen mode

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;
  }
`;
// ...
Enter fullscreen mode Exit fullscreen mode

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)}
      // ...
    />
  ));
}
// ...
Enter fullscreen mode Exit fullscreen mode

And now, the slideshow gallery is ready! Take a look at the final result:

slideshow gallery

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!

Top comments (0)