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.
//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;
|
/* 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.3 s;
}
.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.3 s, transform 0.2 s;
}
.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.3 s;
}
.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.3 s, transform 0.2 s;
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.3 s;
}
.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 ;
}
}
|
Steps to run the application:
npm start
Output: Type the following URL in the address bar http://localhost:3000/