How to Upload, Crop, & Resize Images in the Browser in Nextjs
Two of the most fundamental image editing functions are resizing and cropping. Both must be carefully considered because they have the potential to degrade image quality.
Cropping always includes removing a portion of the original image and losing some pixels.
This post will teach us how to upload, crop, resize and image in the browser.
This project was completed in a Codesandbox. To start quickly, fork the Codesandbox or run the project.
Prerequisite
The following steps in this post require JavaScript and React.js experience. Experience with Next.js isn't a requirement, but it's nice.
We also need a Cloudinary account to store the media files.
Cloudinary offers a safe and complete API for quickly and efficiently uploading media files from the server, browser, or a mobile application.
Next.js is an open-source React-based front-end development web framework that allows server-side rendering and the generation of static websites and applications.
Project Setup and Installation
We use the npx create-next-app
command to scaffold a new project in a directory of our choice to create a new project.
We do this with the command:
npx create-next-app <project name>
To install the dependencies, we use the commands:
cd <project name>
npm install cloudinary-react
Once the app is created and the dependencies are installed, we will see a message with instructions for navigating to our site and running it locally. We do this with the command.
npm run dev
Next.js will start a hot-reloading development environment accessible by default at http://localhost:3000
Building the user interface
We require the user interface to upload, crop, and resize images on the home page. We will do this by updating the pages/index.js
file to a component:
import React, { useState } from "react";
import Head from "next/head";
const IndexPage = () => {
return (
<>
<div className="main">
<div className="splitdiv" id="leftdiv">
<h1 className="main-h1">
How to Crop, Resize & Upload Image in the Browser using Cloudinary
Transformation
</h1>
<div id="leftdivcard">
<h2 className="main-h2">Resize Options</h2>
</div>
<button type="button" id="leftbutton">
Upload Image
</button>
</div>
<div className="splitdiv" id="rightdiv">
<h1> Image will appear here</h1>
</div>
</div>
</>
);
};
export default IndexPage;
The current user interface doesn't look aesthetically pleasing. We'll add some styling with CSS in the style.css
file with the following styles.
@import url("https://fonts.googleapis.com/css?family=Acme|Lobster");
/* This allows me to have the full width of the page without the initial padding/margin*/
body,
html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
font-family: Acme;
min-width: 700px;
}
.splitdiv {
height: 100%;
width: 50%;
}
/* This part contains all of the left sides of the screen */
/* ----------------------------------------- */
#leftdiv {
float: left;
background-color: #fafafa;
height: 932px;
}
#leftdivcard {
margin: 0 auto;
width: 50%;
background-color: white;
margin-top: 25vh;
transform: translateY(-50%);
box-shadow: 10px 10px 1px 0px rgba(78, 205, 196, 0.2);
border-radius: 10px;
}
#leftbutton {
background-color: #512cf3;
border-radius: 5px;
color: #fafafa;
margin-left: 350px;
}
/* ----------------------------------------- */
/* This part contains all of the left sides of the screen */
/* ----------------------------------------- */
#rightdiv {
float: right;
background-color: #cbcfcf;
height: 932px;
}
#rightdivcard {
margin: 0 auto;
width: 50%;
margin-top: 50vh;
transform: translateY(-50%);
background-position: bottom;
background-size: 20px 2px;
background-repeat: repeat-x;
}
/* ----------------------------------------- */
/* Basic styling */
/* ----------------------------------------- */
button {
outline: none !important;
font-family: Lobster;
margin-bottom: 15px;
border: none;
font-size: 20px;
padding: 8px;
padding-left: 20px;
padding-right: 20px;
margin-top: -15px;
cursor: pointer;
}
h1 {
font-family: Lobster;
color: #512cf3;
text-align: center;
font-size: 40px;
}
input {
font-family: Acme;
font-size: 16px;
font-family: 15px;
}
input {
width: 30%;
height: 20px;
padding: 16px;
margin-left: 1%;
margin-right: 2%;
margin-top: 15px;
margin-bottom: 10px;
display: inline-block;
border: none;
}
input:focus {
outline: none !important;
border: 1px solid #512cf3;
box-shadow: 0 0 1px round #719ece;
}
/* ----------------------------------------- */
.main {
height: 100%;
width: 100%;
display: inline-block;
}
.main-h2 {
padding-top: 20px;
text-align: center;
}
.body-h1 {
padding-top: 20px;
text-align: center;
color: white;
}
.inner-p {
color: white;
text-align: center;
}
.main-align {
text-align: center;
}
.form-control {
margin-left: 15px;
}
Our application should now look like this on localhost:3000:
Creating the Image Upload Widget
Cloudinary's upload widget lets us upload media assets from multiple sources, including Dropbox, Facebook, Instagram, and images taken right from our device camera. We'll utilize the upload widget in this project.
Create a free cloudinary account to obtain your cloud name and upload_preset.
upload_presets
allows us to define a set of asset upload choices centrally rather than provide them in each upload call. A Cloudinary cloud name
is a unique identifier associated with our Cloudinary account.
First, from a content delivery network (CDN), we will add the Cloudinary widget's JavaScript file in our index.js
located in pages/index.js.
We will include this file using next/head
to include all meta tags, which lets us add data to the Head portion of our HTML document in React.
Next, in the pages/index.js
file, we import Head from next/head and add the script file.
import React, { useState } from "react";
import Head from "next/head";
const IndexPage = () => {
return (
<>
<Head>
<title>How to Crop and Resize Image in the Browser</title>
<link rel="icon" href="/favicon.ico" />
<meta charSet="utf-8" />
<script
src="https://widget.Cloudinary.com/v2.0/global/all.js"
type="text/javascript"
></script>
</Head>
<div className="main">
[...]
</div>
</>
);
};
export default IndexPage;
In the pages/index.js
file, we will create an instance of the widget in a method triggered when clicking a button and a state variable imagePublicId.
import React, { useState } from "react";
import Head from "next/head";
const IndexPage = () => {
const [imagePublicId, setImagePublicId] = useState("");
const openWidget = () => {
// create the widget
const widget = window.cloudinary.createUploadWidget(
{
cloudName: "olanetsoft",
uploadPreset: "w42epls7"
},
(error, result) => {
if (
result.event === "success" &&
result.info.resource_type === "image"
) {
console.log(result.info);
setImagePublicId(result.info.public_id);
}
}
);
widget.open(); // open up the widget after creation
};
return (
<>
//...
</>
);
};
export default IndexPage;
The widget requires our Cloudinary cloud_name
and uploadPreset.
The createWidget()
function creates a new upload widget. On successfully uploading an image, we assign the public_id
of the asset to the relevant state variable.
To get our cloudname
and uploadPreset,
we follow the steps below:
The cloud name is obtained from our Cloudinary dashboard, as shown below.
An upload preset can be found in the Upload
tab of our Cloudinary settings page, which we access by clicking on the gear icon in the top right corner of the dashboard page.
We scroll down to the bottom of the page to the upload presets section, where we see our upload preset or the option to create one if we don't have any.
We proceed to call the openWidget
function in the onClick
handler of our image upload button, as shown below:
//...
const IndexPage = () => {
//...
return (
<>
//....
<div className="main">
<div className="splitdiv" id="leftdiv">
//...
<div id="leftdivcard">
<h2 className="main-h2">Resize Options</h2>
//...
</div>
<button type="button" id="leftbutton" onClick={openWidget}>
Upload Image
</button>
</div>
<div className="splitdiv" id="rightdiv">
<h1> Image will appear here</h1>
</div>
</div>
</>
);
};
export default IndexPage;
When we open our app in the browser and click the Upload Image
button, we should see something like this:
Implement Custom Transformation Functions
We need to create a component that handles the transformation depending on the props passed to it. We will create a components/
directory in the root folder, and inside it, we will create a file called image.js
with the following content.
import { CloudinaryContext, Transformation, Image } from "cloudinary-react";
const TransformImage = ({ crop, image, width, height }) => {
return (
<CloudinaryContext cloudName="olanetsoft">
<Image publicId={image}>
<Transformation width={width} height={height} crop={crop} />
</Image>
</CloudinaryContext>
);
};
export default TransformImage;
In the code snippet above, we imported CloudinaryContext
, a wrapper Cloudinary component used to manage shared information across all its children Cloudinary components. The rendered TransformImage
component takes data of the image transformation as props.
The above code block will render the uploaded Image when we import it into pages/index.js
:
//...
import TransformImage from "../components/image";
const IndexPage = () => {
const [imagePublicId, setImagePublicId] = useState("");
const [alt, setAlt] = useState("");
const [crop, setCrop] = useState("scale");
const [height, setHeight] = useState(200);
const [width, setWidth] = useState(200);
return (
<>
//...
<div className="main">
<div className="splitdiv" id="leftdiv">
//...
</div>
<div className="splitdiv" id="rightdiv">
<h1> Image will appear here</h1>
<div id="rightdivcard">
{imagePublicId ? (
<TransformImage
crop={crop}
image={imagePublicId}
width={width}
height={height}
/>
) : (
<h1> Image will appear here</h1>
)}
</div>
</div>
</div>
</>
);
};
export default IndexPage;
Next, we will add the Resize Options
radio button so that we can select different resize and crop options with the following code snippet.
//...
const IndexPage = () => {
//...
return (
<>
//...
<div className="main">
<div className="splitdiv" id="leftdiv">
//...
<div id="leftdivcard">
<h2 className="main-h2">Resize Options</h2>
<label className="form-control">Select Crop Type</label>
<div>
<label className="form-control">Scale</label>
<input
type="radio"
value="scale"
name="crop"
onChange={(event) => setCrop(event.target.value)}
/>
</div>
<div>
<label className="form-control">Crop</label>
<input
type="radio"
value="crop"
name="crop"
onChange={(event) => setCrop(event.target.value)}
/>
</div>
<input
type="number"
placeholder="Height"
onChange={(event) => setHeight(event.target.value)}
/>
<input
type="number"
placeholder="Width"
onChange={(event) => setWidth(event.target.value)}
/>
</div>
<button type="button" id="leftbutton" onClick={openWidget}>
Upload Image
</button>
</div>
<div className="splitdiv" id="rightdiv">
//...
</div>
</div>
</>
);
};
export default IndexPage;
In the code snippet above, we
Added crop type and also width and height options
Added an
onChange
property to keep track of the changes in the height and width input field, respectively
Our application's final output should look similar to what we have below.
GitHub Repository: https://github.com/Olanetsoft/how-to-upload-crop-and-resize-images-in-the-browser-in-next.js
Conclusion
This post shows how to upload, crop, & resize images in the browser in Next.js.
Resources
You may find these resources helpful.
I'd love to connect with you at Twitter | LinkedIn | GitHub | Portfolio
See you in my next blog article. Take care!!!