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.
Javascript
//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; |
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" /> w3wiki 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; |
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.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/