How to Manage Images in Website – Fullscreen Image Slider CMS With React & ES7
If you find yourself in the situation where you want to provide a cinematic experience for your website’s users, you might consider using a fullscreen image slider.
An interactive gallery will allow users, to have an immersive experience while navigating through image expressing a brand, website or product.
Using the latest in web technologies such as Reactjs this can be achieved with relative ease.
Additionally, we can use an external CMS (Content Management System) like Contentful so we can manage the image externally
Check the demo here.
CMS Slider SPA
This post will demonstrate how to build a Reactjs SPA (Single Page Application) Gallery using Contentful as a CMS.
The code repository is available here.
How to Build A Fullscreen Image Gallery
For the purposes of this post imagine we are trying to build a project for a client. The client requires a single page application where he/she can put in content for an image/video slider.
This project must be:
- A single page web app (SPA)
- Performant
- Using a CMS
This project must be a fluid, instantaneous image management slider. The client wants to avoid loading between slides.
This is achieved with relative ease using React.js & Contentful.
Contentful will act as an external CMS for the application. Its quite a flexible service with a decent free tier so it fits a clients content managing requirements quite well.
Setting up Contentful
In order to build a data structure for the images and text for the gallery. We need to create an account with Contentful.
Once created, you should see a welcome screen like so:
From here we want to create our own space. Click the top righthand corner:
Add a space:
Select the free tier:
Name your space:
And we are now ready to create our data modal for the gallery slides.
Data Modal
When trying to visualize what data is needed for an image management system we must look at the functionality of the project.
We know that each slide will need a:
- Image
- Video
- Title
So our Contentful Modal should look like this:
This is a consistent feature across most CMS systems. They allow the user to define a particular data entity and give it a defined type.
For example, a typical data entity could be a blog post. This could contain a title, description, banner image etc.
So we want to add this gallery entity by navigating to the Content section and clicking add :
We then want to upload the image required for the entity:
This image will represent one slide of the fullscreen gallery.
Creating our React Components
With a fresh installation of create-react-app we want define two components.
- Home.js
- Media.js
Our parent component will be Home.js
, it will send all data to Slide.js
via props. Slide.js
will only contain information for one slide at a time. That means that all manipulation of state will be done in Home.js
.
When creating the state of our component thinking about what is likely to change on screen helps to define what is needed.
So we might need:
- Array of slides
- Boolean to track if a video is playing
- A media index to track which slide should be sent down to
Media.js
this.state = { playVideo: false, videoId: '', videoUrl: '', images: [], mediaIndex: 0 }; }
Retrieving the data
First we need to install the Contentful React package.
npm install contentful
Then, we need to import it into Home.js
import * as contentful from 'contentful'
Since Home.js
, will manage the slide data in its state, we want to retrieve it when it mounts.
We must get our api key from Contentful first.
Navigate to settings – API keys:
You will need both the Space ID and the access token here.
Once we have both of these we can retrieve our data. From Home.js
create the the lifecycle method componentWillMount()
componentWillMount() { var client = contentful.createClient({ space: 'Your Space ID', accessToken: 'You Access token' }) client.getEntries().then(entries => { entries.items.forEach(entry => { if(entry.fields) { console.log(entry.fields); this.setState({images: entry.fields}) } }) }) }
We are using the getEntries()
method that Contentful package provides for us with our account info.
This returns a promise and in .then()
we iterate through our entries and apply them to the local state of Home.js
.
Great, now we have all the custom data in our component and can now interact with it.
Defining actions
We can easily define what actions the Slide
component will have by imagining how the user will interact with the gallery.
An image management app will have actions for:
- Going to the next slide
- Going to the previous slide
- Playing the video (Later on in this tutorial)
- Closing the slide (To go back to main screen)
Remember, the Slide
component will never handle these actions itself but rather it will send callbacks up to Home.js
. to make adjustment to its state
The Slide
components props look like this:
<Slide onClose={() => this.closeSlide()} onNext={() => this.nextSlide() } onPrev={() => this.prevSlide()} playNext={() => this.nextSlide() />
Each one of these actions will manipulate our slide data array.
nextVideo()
This is event occurs on right arrow click. We make sure that there is another slide available and increment the index if so.
nextVideo() { // If not last slide if(this.state.mediaIndex < this.state.images.length -1 ) { this.setState({mediaIndex: this.state.mediaIndex + 1}) } // Otherwise we go back to home screen else { this.setState({ playSlide: false, mediaIndex: 0 }); } }
prevVideo()
This is event occurs on left arrows click. Check to see that we are not on first slide, else decrement our position in the slides array to show the previous item.
if( this.state.mediaIndex > 0 ) { this.setState({mediaIndex: this.state.mediaIndex - 1}) } // Otherwise we go back to home screen else { this.setState({ playVideo: false }); } }
closeSlide()
This is invoked from an event by clicking the close button on a slide.
Reset slide index and stop showing the slide component.
closeSlide() { this.setState({ playVideo: false, mediaIndex: 0 }); }
Previous/Next
This sets us up nicely with a basic image slider. But we want to hide the previous and next arrows if no slides exists based on the index of the slides array in our state.
This can be done by adding noLast and noFirst props so that our Slides
component can show/hide these accordingly.
<Slide onClose={() => this.closeVideo()} onNext={() => this.nextVideo() } onPrev={() => this.prevVideo()} playNext={() => this.nextVideo()} videoUrl={this.state.images[mediaIndex].video} imageUrl={this.state.images[mediaIndex].url} noLast={this.state.mediaIndex === this.state.images.length -1} noFirst={this.state.mediaIndex === 0} />
Its surprising just how much of the UI can be correctly manipulated by determining the current position an array in a component’s state.
By having these calculations take place in Home.js
, the Slide
component can just rely on these explicit props to show/hide different parts of the UI.
Background Video
We have successfully created a image slider application with our CMS data.
Lets now create a video that plays over the background image.
First, create the play button:
import React from 'react'; const playButton = props => ( <div onClick={() => props.onPlay()} class="play-button-outer"> <div class="play-button" /> </div> ); export default playButton;
This a dumb stateless component, that we will import into Slide.js
.
import PlayButton from './playButton';
Put in in our render method:
<PlayButton onPlay={() => this.playVideo()} />
This will invoke a function to play the video:
playVideo() { this.setState({ shouldPlay: true }); }
{ this.state.shouldPlay ? ( <video style={{ objectFit: 'cover', width: '100%', height: '100%'}} onPause={() => this.setState({paused: true})} onPlay={() => this.setState({paused: false})} controls ref={element => (this.video = element)} key={vid} autoPlay={true} onEnded={() => this.playNext()} id="background-video" autoPlay > <source src={vid} type="video/mp4" /> Your browser does not support the video tag. </video> ) : null}
The video
element has a considerable amount of events that you can call actions upon. Events like onPlay()
, onPause()
, onEnded()
can be extremely useful when you want to change the state of your component to reflect the status video.
This video will play in the background with the following styles:
.video-wrapper { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; -ms-flex-direction: row; background-size:cover; background-repeat: no-repeat !important; flex-direction: row; min-height:800px; flex:1; position: absolute; right: 0; height: 100%; background-size: cover; left: 0; bottom: 0; top: 0; background-size: auto 100%; }
You can check the full styling details in the repo.
Fullscreen Mode
Perhaps, one of the more tricky elements to this application is enabling fullscreen mode.
The client may want a fullscreen application to display the webapp on an iPad.
There are some considerations here, firstly, the JavaScript Full Screen API is only available to use upon user input.
This means that the user has to initiate an action before application can go fullscreen. This cannot be avoided due to security issues if a website is able to go full screen instantly.
So, when the user clicks the main How does it Work or Learn More buttons we will invoke the FullScreen API.
In Tile.js
<div className="options"> <div className="play"> <div onClick={() => this.props.onPlay( 0 )}> <img src={workButton} /> </div> </div> </div>
The onClick()
event will will go back up to Home.js
and initiate the fullscreen code
playSlide(index) { this.setState({ playVideo: true, mediaIndex: index }); var elem = document.body; if (elem.requestFullscreen) { elem.requestFullscreen(); } else if (elem.mozRequestFullScreen) { /* Firefox */ elem.mozRequestFullScreen(); } else if (elem.webkitRequestFullscreen) { /* Chrome, Safari & Opera */ document.body.webkitRequestFullscreen(); } else if (elem.msRequestFullscreen) { /* IE/Edge */ elem.msRequestFullscreen(); } }
This piece of functionality covers all browser cases and will initialise the full screen mode. This is ideal for a Gallery/Slider on an Ipad and in some cases on desktop too.
On Tablet it is important to remember to change the manifest.json
file for full screen capabilities. Specifically the property display need to be “fullscreen”.
{ "short_name": "CMS Gallery", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "display": "fullscreen", "theme_color": "#000000", "background_color": "#ffffff" }
Once, on the web app url using a Tablet, simple save the app to your home screen and launch it from there. This simulates opening a native app but in reality you are just opening up a the web apps URL in full screen.
Key takeaways
- Most our UI changes for our data can be based on the current index of our array coming from our Contentful CMS.
- Most of our the actions of the gallery can take place in our parent component (Home.js), these changes to state will be drilled down to the
Slide.js
component via props. - We can specify exactly what happens when a slide starts, when slide finishes etc via React lifecycle hooks. (componentDidMount, componentWillMount, componentWillUnmount)
- A client now has full control of the content of the application, he/she can add as many slides as desired. This show how reusable our
Slide.js
component is. And we can now reuse this component in any other project.
If your interested in an image management system for your website or webapp. Feel free to get in touch.