Image Compressor using ReactJS
This article focuses on crafting an Interactive Feature-based Image Compressor utilizing the ReactJS library. Users can upload image files and adjust compression quality via a slider. Upon setting the compression quality and initiating compression, users can download the compressed image locally. Additionally, the application offers navigation for accessing Instructions and Compression History.
Output Preview: Let us have a look at how the final output will look like.

Prerequisites
Steps to create the React App:
Step 1: Create a React App
npx create-react-app image-compressor
Step 2: Navigate to the newly created project folder by executing the below command.
cd image-compressor
Step 3: Steps to install required modules
npm install react-bootstrap @fortawesome/react-fontawesome @fortawesome/free-solid-svg-icons
npm install image-conversion bootstrap
Project Structure:
The updated dependencies in package.json will look like this:
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.2",
"image-conversion": "^2.1.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Approach to create Image Compressor:
- The application is structured using React components like Buttons, NavBar, Modal, Spinner, etc., along with CSS for styling.
- Image upload triggers validation to ensure only image files can be compressed, with customization options for compression quality.
- Users can download compressed images and access features like viewing instructions and past compression history.
- The interface is organized with sections for navigation, image display/upload, compression controls, and a Reset Button for clearing uploaded files.
Example: Insert the below code in the App.js, Compressor.js, and Compressor.css file mentioned in the above directory structure.
/* Components/Compressor.css */
.center {
text-align: center !important;
}
.mainContainer {
margin: 0;
text-align: center;
}
@media (max-width: 768px) {
.mainContainer {
margin: 0;
}
}
.navbar {
z-index: 1041;
box-shadow: 0 4px 8px rgba(233, 12, 12, 0.2);
background-color: #fff78b !important;
padding: 20px;
}
.navbar-content {
color: green !important;
text-align: center !important;
font-weight: bold;
font-size: 24px;
text-transform: uppercase;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.help-icon,
.history-icon {
font-size: 24px;
cursor: pointer;
margin-right: 20px;
color: #000;
}
.help-icon:hover,
.history-icon:hover {
color: #3498db;
}
.help-container,
.history-container {
text-align: left;
padding: 10px;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 5px;
margin: 10px;
max-height: 200px;
overflow-y: auto;
}
.social-icons {
margin-right: 10px;
box-sizing: border-box;
width: 1.5em !important;
height: 1.5em !important;
color: #ecf0f1;
transition: color 0.3s;
}
.social-icons:hover {
color: #e74c3c;
}
.uploadCard {
width: 80%;
display: inline-block;
}
.image {
display: block;
max-width: 100%;
height: auto;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 1px solid #ecf0f1;
}
.upload-btn-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
}
.btn {
border: none;
color: white;
padding: 10px 20px;
border-radius: 8px;
font-size: 20px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
}
.btn:hover {
background-color: #2980b9;
transform: scale(1.05);
}
.upload-btn-wrapper input[type="file"] {
font-size: 100px;
position: absolute;
left: 0;
top: 0;
opacity: 0;
}
#qualitySlider {
width: 100%;
padding: 0;
margin: 10px 0;
}
.btn.download-btn {
background-color: #2ecc71;
transition: background-color 0.3s;
}
.btn.download-btn:hover {
background-color: #27ae60;
}
.btn-reset {
border: none;
color: white;
background-color: #e74c3c;
padding: 10px 20px;
border-radius: 8px;
font-size: 20px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s;
margin-left: 20px;
}
.btn-reset:hover {
background-color: #c0392b;
transform: scale(1.05);
}
.compressed-message {
font-size: 24px;
font-weight: bold;
color: #2ecc71;
margin-top: 10px;
transition: color 0.3s;
}
.compressed-message:hover {
color: #27ae60;
}
.button-container {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
@media (max-width: 768px) {
.help-container,
.history-container {
width: 100%;
max-width: none;
}
}
//App.js
import React from 'react';
import './App.css';
import CompressorComp
from "./Components/Compressor";
import 'bootstrap/dist/css/bootstrap.css';
function App() {
return (
<CompressorComp />
);
}
export default App;
//Components/Compressor.js
import React,
{
useState, useEffect
} from 'react';
import {
Navbar, Card,
Spinner, Modal,
Button
} from 'react-bootstrap';
import { FontAwesomeIcon }
from '@fortawesome/react-fontawesome';
import {
faImage, faDownload,
faUpload, faImage as faImagePlaceholder,
faQuestionCircle,
faHistory
} from '@fortawesome/free-solid-svg-icons';
import './Compressor.css';
import { compress }
from 'image-conversion';
function CompressorComp() {
const [compressedLink, setCompressedLink] = useState('');
const [originalImage, setOriginalImage] = useState(null);
const [originalLink, setOriginalLink] = useState('');
const [uploadImage, setUploadImage] = useState(false);
const [outputFileName, setOutputFileName] = useState('');
const [compressionQuality, setCompressionQuality] = useState(0.8);
const [originalSize, setOriginalSize] = useState(0);
const [compressedSize, setCompressedSize] = useState(0);
const [isCompressed, setIsCompressed] = useState(false);
const [compressionInProgress, setCompressionInProgress] = useState(false);
const [loading, setLoading] = useState(false);
const [showHelp, setShowHelp] = useState(false);
const [showHistory, setShowHistory] = useState(false);
const [compressedHistory, setCompressedHistory] = useState([]);
const [showCompressedImage, setShowCompressedImage] = useState(false);
const [modalShow, setModalShow] = useState(false);
useEffect(() => {
if (originalImage) {
setCompressedLink('');
setCompressedSize(0);
setIsCompressed(false);
setShowCompressedImage(false);
}
}, [originalImage]);
async function uploadLink(event) {
const imageFile = event.target.files[0];
setOriginalLink(URL.createObjectURL(imageFile));
setOriginalImage(imageFile);
setOutputFileName(imageFile.name);
setUploadImage(true);
setOriginalSize(imageFile.size);
}
async function compressImage() {
if (!originalImage) {
alert('Please upload an image first.');
return;
}
try {
setCompressionInProgress(true);
setShowCompressedImage(false);
setLoading(true);
const compressedImage =
await compress(originalImage, {
quality: compressionQuality,
width: 800,
height: 800,
});
setCompressedLink(URL.createObjectURL(compressedImage));
setCompressedSize(compressedImage.size);
setIsCompressed(true);
setCompressedHistory(
[
...compressedHistory,
{
link: compressedLink,
name: outputFileName
}
]);
setTimeout(
() => {
setLoading(false);
setShowCompressedImage(true);
}, 2000);
} catch (error) {
console.error('Image compression failed:', error);
alert('Image compression failed. Please try again.');
} finally {
setCompressionInProgress(false);
}
}
function resetApp() {
setOriginalLink('');
setOriginalImage(null);
setUploadImage(false);
setOutputFileName('');
setCompressionQuality(0.8);
setOriginalSize(0);
setCompressedSize(0);
setIsCompressed(false);
setCompressedLink('');
setShowCompressedImage(false);
}
function toggleHelp() {
setShowHelp(!showHelp);
}
function toggleHistory() {
setShowHistory(!showHistory);
}
return (
<div className="mainContainer">
<Navbar className="navbar justify-content-between"
bg="lig" variant="dark">
<div>
<Navbar.Brand className="navbar-content">
<center>
<FontAwesomeIcon icon={faImage}
className="icon" />
GeeksforGeeks Image Compressor
</center>
</Navbar.Brand>
</div>
<div className="navbar-actions">
<FontAwesomeIcon icon={faQuestionCircle}
className="help-icon" onClick={toggleHelp} />
<FontAwesomeIcon icon={faHistory}
className="history-icon" onClick={toggleHistory} />
</div>
</Navbar>
{showHelp && (
<div className="help-container">
<p>Instructions:</p>
<ul>
<li>
Upload an image using
the "Upload a file" button.
</li>
<li>
Adjust the compression
quality using the slider.
</li>
<li>
Press the "Compress" button
to start the compression.
</li>
<li>
Download the compressed image
using the "Download" button.
</li>
</ul>
</div>
)}
{showHistory && (
<div className="history-container">
<p>Compressed History:</p>
<ul>
{
compressedHistory.map(
(item, index) => (
<li key={index}>
<a href={item.link}
download={item.name}>
{item.name}
</a>
</li>
))
}
</ul>
</div>
)}
<div className="row mt-5">
<div className="col-xl-3 col-lg-3
col-md-12 col-sm-12">
{uploadImage ? (
<Card.Img className="image"
variant="top" src={originalLink}
alt="Original Image" />
) : (
<Card.Img className="uploadCard"
variant="top" src={faUpload} alt="" />
)}
<div className="d-flex justify-content-center
upload-btn-wrapper">
<label htmlFor="uploadBtn"
className="btn btn-primary">
<FontAwesomeIcon icon={faUpload}
className="icon" />
Upload a file
</label>
<input
type="file"
id="uploadBtn"
accept="image/*"
className="mt-2 btn btn-primary w-75"
onChange={(event) => uploadLink(event)} />
</div>
</div>
<div
className="col-xl-6 col-lg-6
col-md-12 col-sm-12
d-flex justify-content-center
align-items-baseline">
<div>
{outputFileName ? (
<div>
<label htmlFor="qualitySlider">
Compression Quality:
</label>
<input
id="qualitySlider"
type="range"
min="0.1"
max="1"
step="0.1"
value={compressionQuality}
onChange={
(event) =>
setCompressionQuality(
parseFloat(event.target.value)
)
}
/>
<div className="text-center">
Original Size:
{
Math.round(originalSize / 1024)
} KB
<br />
Compressed Size:
{
Math.round(compressedSize / 1024)
} KB
</div>
<div className="text-center">
{isCompressed &&
!compressionInProgress && (
<div className="text-success
compressed-message">
Image compressed successfully!
</div>
)}
{
compressionInProgress &&
<div className="text-info">
Compressing image...
</div>
}
</div>
<div className="button-container">
{loading ? (
<div className="text-info">
Loading...
</div>
) : (
<button type="button"
className="btn btn-success"
onClick={compressImage}>
<FontAwesomeIcon icon={faImage}
className="icon" />
Compress
</button>
)}
<button type="button"
className="btn btn-danger ml-3"
onClick={resetApp}>
Reset
</button>
</div>
</div>
) : (
<></>
)}
</div>
</div>
<div className="col-xl-3 col-lg-3 col-md-12 col-sm-12">
{showCompressedImage ? (
<div>
<Card.Img
className="image"
variant="top"
src={compressedLink}
alt="Compressed Image"
onClick={() => setModalShow(true)}
style={{ cursor: 'pointer' }}
/>
<a href={compressedLink}
download={outputFileName}
className="mt-2 btn btn-success
w-75 download-btn">
<FontAwesomeIcon icon={faDownload}
className="icon" />
Download
</a>
<Modal show={modalShow}
onHide={
() =>
setModalShow(false)
} size="lg">
<Modal.Body className="text-center">
<Card.Img className="image"
variant="top" src={compressedLink}
alt="Compressed Image" />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary"
onClick={
() => setModalShow(false)
}>
Close
</Button>
</Modal.Footer>
</Modal>
</div>
) : (
<div className="d-flex align-items-center
justify-content-center">
{
compressionInProgress &&
<Spinner animation="border" variant="primary" />
}
{
!uploadImage &&
!compressionInProgress && (
<FontAwesomeIcon icon={faImagePlaceholder}
className="icon" size="3x" />
)
}
</div>
)}
</div>
</div>
</div>
);
}
export default CompressorComp;
Steps to run the application:
npm start
Output: Type the following URL in the address bar http://localhost:3000/