We’ve all seen and used them… animated GIFs they are funny, distracting and seem to be everywhere. We have Alex Chung and Jace Cooke to thank for this incredible distraction and waste of everyone’s time. Seriously. It’s a distraction. The initial idea came to them over breakfast. There is now over 250 million users, and the company raised $55 million in funding at a $300 million valuation.
Founder Alex Chung - 2016 SXSW
Wouldn’t it be awesome if you could build your own Giphy Alternative in about the same amount of time it takes to eat your frosted flakes?
What We'll Build
You have been tasked with creating a Giphy Alternative in record time and immediately wonder how to do that. The client can’t wait and you need something to show to them ASAP.
Well, look no further; In this tutorial, we will be building our version of Giphy which utilizes Cloudinary for the hosting and transformation of our files. We’ll also be able to create tiny GIFs that we can share with the world.
By the end of this tutorial, our app will have the following features:
- Users should be able to sign up and log in.
- Registered users should be able to upload animated GIFs (hosted GIFs included) via the platform.
- Registered and unregistered users should be able to view all the animated GIFs on a dashboard.
- Users should able to share those GIFs on Facebook and Twitter.
- Users will be able to convert videos to GIFs and download them.
Shall we begin?
Setting Up
We will build our app using React and set it up using the create-react-app tool. Just navigate to your dev folder and run the command below in your terminal.
create-react-app cliphy
cd cliphy
Let’s use bootstrap and material bootstrap theme to style the application.
For bootstrap, open up the index.html
in the public folder of the application and add the following line where you would normally put your styles:
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
Grab the CSS for the bootstrap material design, copy it to your public/css folder
and reference it in your index.html:
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap-material-design.min.css">
Authentication
Install auth0-js and the following modules with the following command:
npm i auth0-js [email protected] jwt-decode axios
auth0-js - For authentication
react-router - For routing within our app
jwt-decode - For decoding the JSON Web Token in our app
axios - For making network requests.
Create components
and utils
folders under your src
directory. In the utils folder, create a file AuthService.js and add this code to it. (See the ReactJS Authentication Tutorial by unicodeveloper to ensure you are on the right track).
Creating Our Components
First let’s add Navigation and then create the different components that we need in our application. Now create a Nav.js in the components
folder and the following piece of code to it.
import React, { Component } from 'react';
import { Link } from 'react-router';
import { login, logout, isLoggedIn } from '../utils/AuthService';
import { uploadWidget } from '../utils/WidgetHelper';
import '../App.css';
class Nav extends Component {
render() {
return (
<nav className="navbar navbar-inverse">
<div className="container-fluid">
<div className="navbar-header">
<Link className="navbar-brand" to="/">Cliphy</Link>
</div>
<div>
<ul className="nav navbar-nav">
<li>
<Link to="/">All Gifs</Link>
</li>
<li>
<Link to="/create">Create Gif</Link>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li>
{
(isLoggedIn()) ? <button type="button" className="btn btn-raised btn-sm btn-default" >Upload Gif</button> : ''
}
</li>
<li>
{
(isLoggedIn()) ?
(
<button type="button" className="btn btn-raised btn-sm btn-danger" onClick={() => logout()}>Log out </button>
) : (
<button className="btn btn-sm btn-raised btn-default" onClick={() => login()}>Log In</button>
)
}
</li>
</ul>
</div>
</div>
</nav>
)
}
}
export default Nav;
Go ahead and create a Dashboard.js
file and add this code to it:
import React, { Component } from 'react';
import { Link } from 'react-router';
import axios from 'axios';
import Nav from './Nav';
class Dashboard extends Component {
state = {
gifs: []
};
render() {
const { gifs } = this.state;
return (
<div>
<Nav />
<div className="row">
<h3 className="col-md-12"> The Dashboard</h3>
</div>
</div>
);
}
}
export default Dashboard;
Let’s create another component named create.js
. This component will be empty for now. We’ll come back to it later.
components/Create.js
import React, { Component } from 'react';
import Nav from './Nav';
class Create extends Component {}
export default Create;
Uploading GIFs
As mentioned earlier, we will be using Cloudinary to handle our images, including uploading and manipulating them. To start, let’s create an account on cloudinary.com.
Uploading with Cloudinary is a breeze, using the simple Upload Widget. It enables you to upload files from various sources. So we are going to reference it in our application. Open your index.html
file and add the following code to the bottom of your page.
<script src="//widget.cloudinary.com/global/all.js" type="text/javascript"></script>
Now, create a file called WidgetHelper.js
in the utils
folder and add the following piece of code to it.
export function uploadWidget(cloudinarySettings, cb) {
window.cloudinary
.openUploadWidget(cloudinarySettings, (err, res) => {
console.error(err);
cb(res);
});
}
We will make some changes to our Nav.js to trigger the upload widget. Modify your Nav.js file to look like this:
import React, { Component } from 'react';
import { Link } from 'react-router';
import { login, logout, isLoggedIn } from '../utils/AuthService';
import { uploadWidget } from '../utils/WidgetHelper';
import '../App.css';
import Create from './Create';
class Nav extends Component {
uploadGif() {
let cloudinarySettings = {
cloud_name: '<CLOUD_NAME>',
upload_preset: '<UPLOAD_PRESET>',
tags: ['cliphy'],
sources: ['local', 'url', 'google_photos', 'facebook'],
client_allowed_formats: ['gif'],
keep_widget_open: true,
theme: 'minimal',
}
uploadWidget(cloudinarySettings, (res) => {
console.log(res);
});
}
render() {
return (
<nav className="navbar navbar-inverse">
<div className="container-fluid">
<div className="navbar-header">
<Link className="navbar-brand" to="/">Cliphy</Link>
</div>
<div>
<ul className="nav navbar-nav">
<li>
<Link to="/">All Gifs</Link>
</li>
<li>
<Link to="/create">Create Gif</Link>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li>
{
(isLoggedIn()) ? <button type="button" className="btn btn-raised btn-sm btn-default" onClick={this.uploadGif}>Upload Gif</button> : ''
}
</li>
<li>
{
(isLoggedIn()) ?
(
<button type="button" className="btn btn-raised btn-sm btn-danger" onClick={() => logout()}>Log out </button>
) : (
<button className="btn btn-sm btn-raised btn-default" onClick={() => login()}>Log In</button>
)
}
</li>
</ul>
</div>
</div>
</nav>
)
}
}
export default Nav;
The _cloudname and _uploadpreset options are the only mandatory properties required to make uploads work, but we have set a few more options. Most important to note now is the tags, which we will use to work some magic.
Now, login to the application and then click on the upload button to upload a new GIF file. Note that I have added some restrictions to the type of file that can be uploaded using the _client_allowedformats option of the upload widget.
You can create a new upload preset from your Cloudinary dashboard. Do make sure it is an unsigned one. You also can check out your uploads from your dashboard.
We have added a callback to our uploadWidget helper, which takes in the result object, so we can work with it.
Displaying Our GIFs
We have uploaded GIFs, but they are nowhere to be found on our dashboard. There are several ways this can be achieved. But since we are using React, we are in luck.
Cloudinary provides a simple React component that is capable of handling the display of all our files. All we need to supply it is the publicId of our file on the cloud. Now remember that tag property we mentioned earlier during upload, here’s where we perform some magic with it.
First off, let us install the Cloudinary React component.
npm install cloudinary-react
With this installed, we can retrieve all the images that we uploaded that bears the tag cliphy
.
There’s a simple way to do this - make a get request to a URL, such as this http://res.cloudinary.com/[CLOUD_NAME]/image/list/cliphy.json.
Doing so returns many details about the GIF files we have uploaded, including the publicId of each of them.
Another thing to note: the last part of the URL is the tag name we supplied during upload.
Now let us modify components/Dashboard.js to look like this:
import React, { Component } from 'react';
import { Link } from 'react-router';
import { CloudinaryContext, Transformation, Image } from "cloudinary-react";
import axios from 'axios';
import { isLoggedIn } from '../utils/AuthService';
import Nav from './Nav';
class Dashboard extends Component {
state = {
gifs: []
};
getGifs() {
axios.get('http://res.cloudinary.com/[CLOUD_NAME]/image/list/cliphy.json')
.then(res => {
this.setState({ gifs: res.data.resources })
});
}
componentDidMount() {
this.getGifs();
}
render() {
const { gifs } = this.state;
return (
<div>
<Nav />
<div className="row">
<h3 className="col-md-12"> The Dashboard</h3>
<CloudinaryContext cloudName="[CLOUD_NAME]">
{
gifs.map((data, index) => (
<div className="col-md-4 col-sm-6 col-xs-12" key={index}>
<div className="panel panel-default">
<div className="panel-body">
<div className="embed-responsive embed-responsive-16by9">
<Image className="img-responsive" publicId={data.public_id}></Image>
</div>
</div>
<div className="panel-footer">
</div>
</div>
</div>
))
}
</CloudinaryContext>
</div>
</div>
);
}
}
export default Dashboard;
You should now see all the GIFs displayed in a very beautiful grid.
Sharing to Social Media
Now we will implement sharing on social media. First we install react-share component.
npm install react-share
Now, copy and paste the following codes to the top of the components/Dashboard.js file.
import { ShareButtons, generateShareIcon, ShareCounts } from 'react-share';
const {
FacebookShareButton,
TwitterShareButton,
} = ShareButtons;
Now add the following piece of code within the div with class panel-footer.
Share on:
<TwitterShareButton className="label label-info" title={"Cliphy"} url={`http://res.cloudinary.com/[CLOUD_NAME]/image/upload/${data.public_id}.gif`}>
Twitter </TwitterShareButton>
<FacebookShareButton className="label label-default" url={`http://res.cloudinary.com/[CLOUD_NAME]/image/upload/${data.public_id}.gif`}>
Facebook
</FacebookShareButton>
On reload, you should now have both share buttons for Facebook and Twitter.
Making our own GIFs
It has been nice so far creating our own lovely GIF platform. But it would be even more awesome to be able to create our own GIFs from video files that we can then save to our computers.
Let’s go ahead and build a GIF maker into our application and see how Cloudinary takes away much of the effort that would have been required to build this.
Go ahead and open the empty components/Create.js file we created earlier and add the following piece of code to it.
import React, { Component } from 'react';
import { uploadWidget } from '../utils/WidgetHelper';
import Nav from './Nav';
class Create extends Component {
state = {
gifUrl: "",
startTime: 0,
endTime: 0,
isResult: false,
};
setStartTime = (e) => {
this.setState({ startTime: e.target.value });
}
setEndTime = (e) => {
this.setState({ endTime: e.target.value });
}
createGif = () => {
let cloudinarySettings = {
cloud_name: '<CLOUD_NAME>',
upload_preset: '<UPLOAD_PRESET>',
sources: ['local'],
client_allowed_formats: ['mp4', 'webm'],
keep_widget_open: false,
multiple: false,
theme: 'minimal',
}
uploadWidget(cloudinarySettings, (res) => {
if (res && res[0] !== undefined) {
this.setState({ isResult: true });
this.setGifString(res[0].public_id);
}
console.log(res);
});
}
setGifString = (uploadedVideoId) => {
this.setState({
gifUrl: `http://res.cloudinary.com/[CLOUD_NAME]/video/upload${(this.state.startTime > 0 && this.state.endTime > 0) ? '/so_' + this.state.startTime + ',eo_' + this.state.endTime : ''}/${uploadedVideoId}.gif`
});
}
render() {
return (
<div>
<Nav />
<div className="col-md-6 col-md-offset-3">
<div className="well well-sm">
<form className="form-horizontal">
<legend>Enter start and stop time for animation and hit upload to select file</legend>
<div className="form-group">
<label htmlFor="start" className="col-md-2 control-label">Start</label>
<div className="col-md-10">
<input type="number" value={this.state.startTime} onChange={this.setStartTime} className="form-control" id="start"></input>
</div>
</div>
<div className="form-group">
<label htmlFor="end" className="col-md-2 control-label">End</label>
<div className="col-md-10">
<input type="number" value={this.state.endTime} onChange={this.setEndTime} className="form-control" id="end"></input>
</div>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="button" className="btn btn-raised btn-primary" onClick={this.createGif}>Create</button>
</div>
</div>
</form>
</div>
<div className="panel panel-default">
<div className="panel-body">
{
(this.state.isResult) ?
<img className="img-responsive" src={this.state.gifUrl}></img> : <span className="label label-info">Kindly upload an mp4 video to create Gif</span>
}
</div>
<div className="panel-footer">
</div>
</div>
</div >
</div >
);
}
}
export default Create;
And just like that you have your own GIF maker.
What’s going on here, why does it seem so simple and straightforward? Well, let me explain the various pieces of code I consider important.
First, our reuse of uploadWidget from utils/WidgetHelper.js. We included it in our component and it is triggered when createGif is called. One notable change I’ve made in the options this time around is to only allow mp4 and webm formats. Go ahead and add other video formats if you feel the need to.
Now take a look at this segment:
setGifString = (uploadedVideoId) => {
this.setState({
gifUrl: `http://res.cloudinary.com/[CLOUD_NAME]/video/upload${(this.state.startTime > 0 && this.state.endTime > 0) ? '/so_' + this.state.startTime + ',eo_' + this.state.endTime : ''}/${uploadedVideoId}.gif`
});
}
There is just more magic going on here. Cloudinary is able to serve your videos as GIFs and all you have to do is append the extension .gif to the end of the resource url from Cloudinary. The so and eo parameters specify the start offset and end offset, meaning a specified start time to a stop time duration of the video will be converted to a GIF.
Conclusion
Now you have a fully functional clone of your Giphy ready for a demo and your very own GIF maker to go with it. What more could you ask for? Cloudinary provides us with limitless possibilities with file management and a lot of other awesomeness if you’re working with media files.
You should definitely go play with some more of the features that could be found in their documentation.
The source code of the project is on GitHub.
Let me know in the comments below if you have any questions or challenges.