14
loading...
This website collects cookies to deliver better user experience
The complete code is in this repo.
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;
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;
.<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.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>
);
};
//...
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;
`}
`;
// ...
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.// 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);
`;
// ...
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;
Slideshow
component:// src/slideshow-gallery/index.js
// ...
import data from './data';
const View = () => <Slideshow items={data} />;
// ...
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>
);
};
// ...
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>
);
};
// ...
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;
}
`;
// ...
moveTo
function:// src/slideshow-gallery/index.js
// ...
{
items.map((item, index) => (
<Thumbnail
onClick={moveTo(index)}
// ...
/>
));
}
// ...