Image Compressor using ReactJS (original) (raw)
Last Updated : 25 Jul, 2024
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.
CSS `
/* 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; } }
JavaScript
//App.js import React from 'react'; import './App.css'; import CompressorComp from "./Components/Compressor"; import 'bootstrap/dist/css/bootstrap.css'; function App() { return ( ); } export default App;
JavaScript
//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/