DEV Community

Cover image for How to create an image editor using cropper.js
Ashutosh Tiwari
Ashutosh Tiwari

Posted on • Originally published at incoderweb.blogspot.com

How to create an image editor using cropper.js

Hello friends, today in this blog, we will learn how to create an image editor using cropper.js. In our previous blog, we saw what are the top 5 programming languages to learn in 2023. You can check my other javascript projects after reading this blog.

In today's digital age, images are an essential part of our lives. Whether it's for personal use, social media, or business, images play a crucial role in conveying information and emotions. With the rise of social media platforms like Instagram and Snapchat, image editing has become an even more popular pastime. However, the options for editing images can be limited, and often the software can be expensive or difficult to use.

This is where Cropper.js comes in. Cropper.js is a powerful JavaScript library that allows you to easily create your own custom image editor. With Cropper.js, you can add various features like cropping, resizing, filtering, and more, to your image editor. In this blog post, we will take you through the step-by-step process of creating an image editor using Cropper.js. We'll cover everything from setting up the HTML and CSS files to adding functionality like image upload and download and customizing the interface to make it your own.

By the end of this blog post, you'll have a functional image editor that you can use to edit images for personal use or even integrate into your website or web application. So let's get started on this exciting journey of creating your very own image editor with Cropper.js!

You may like these:

Note:
You can check the live demo and download code files here.

Code of HTML, CSS, and JavaScript Files

Here's the good news: you don't have to write all the code of this project from scratch! I have created a GitHub repository that contains all the HTML, CSS, and JavaScript code needed to build the app. You can check it out and use it as a starting point for your own project.

HTML CODE

<!DOCTYPE html>  
 <html lang="en">  
 <head>  
   <!-- --------------------- Created By InCoder --------------------- -->  
   <meta charset="UTF-8">  
   <meta http-equiv="X-UA-Compatible" content="IE=edge">  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>Image Editor - InCoderWeb</title>  
   <link rel="stylesheet" href="main.css">  
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">  
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.css">  
 </head>  
 <body>  
   <div class="header">  
     <div class="title">Image Editor</div>  
     <div class="options">  
       <input type="file" hidden>  
       <button class="inBtn openNewFile" title="Open Image File"><i class="fa-solid fa-file-image"></i></button>  
       <a href="https://github.com/InCoderWeb" target="_blank" title="Github Profile"><button class="inBtn"><i class="fa-brands fa-github"></i></button></a>  
       <button class="inBtn outputDownloadBtn hide"><i class="fa-solid fa-download"></i></button>  
     </div>  
   </div>  
   <div class="mainContainer">  
     <div class="dragOrDropContainer">  
       <div class="icon"><i class="fa-solid fa-cloud-arrow-up"></i></div>  
       <div class="text">Drag & Drop to Upload File</div>  
     </div>  
     <div class="editorOptions">  
       <div class="sideBar active">  
         <button class="sidebarToggleBtn"><i class="fa-solid fa-chevron-left"></i></button>  
         <div class="preview">  
           <p>Preview</p>  
           <div class="previewImage"></div>  
         </div>  
         <div class="options">  
           <div class="zoom">  
             <p>Zoom In / Zoom Out</p>  
             <div class="btnWrapper">  
               <button class="optionBtn zoomIn"><i class="fa-solid fa-magnifying-glass-plus"></i></button>  
               <button class="optionBtn zoomOut"><i class="fa-solid fa-magnifying-glass-minus"></i></button>  
             </div>  
           </div>  
           <div class="rotate">  
             <p>Rotate Image</p>  
             <div class="btnWrapper">  
               <button class="optionBtn rotateLeft"><i class="fa-solid fa-rotate-left"></i></button>  
               <button class="optionBtn rotateRight"><i class="fa-solid fa-rotate-right"></i></button>  
             </div>  
           </div>  
           <div class="flip">  
             <p>Flip Image</p>  
             <div class="btnWrapper">  
               <button class="optionBtn flipLeftRight"><i class="fa-solid fa-arrows-left-right"></i></button>  
               <button class="optionBtn flipUpDown"><i class="fa-solid fa-arrows-up-down"></i></button>  
             </div>  
           </div>  
           <div class="aspectRatio">  
             <p>Aspect Ratio</p>  
             <div class="btnWrapper">  
               <button class="optionBtnSqr">16:9</button>  
               <button class="optionBtnSqr">4:5</button>  
               <button class="optionBtnSqr">1:1</button>  
               <button class="optionBtnSqr">2:3</button>  
               <button class="optionBtnSqr">Free</button>  
             </div>  
           </div>  
           <div class="dragMode">  
             <p>Drag Mode</p>  
             <div class="btnWrapper">  
               <button class="optionBtn dragModeBtn"><i class="fa-solid fa-crop-simple"></i></button>  
               <button class="optionBtn dragModeBtn"><i class="fa-solid fa-arrows-up-down-left-right"></i></button>  
             </div>  
           </div>  
           <div class="controlCropper">  
             <p>Control Cropper</p>  
             <div class="btnWrapper">  
               <button class="optionBtn cropperClear"><i class="fa-solid fa-bars-staggered"></i></button>  
               <button class="optionBtn cropperCrop"><i class="fa-solid fa-crop-simple"></i></button>  
             </div>  
           </div>  
         </div>  
       </div>  
     </div>  
   </div>  
   <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.js"></script>  
   <script src="script.js"></script>  
 </body>  
 </html>  
Enter fullscreen mode Exit fullscreen mode

CSS CODE

@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");  
 /* --------------------- Created By InCoder --------------------- */  
 * {  
  margin: 0;  
  padding: 0;  
  box-sizing: border-box;  
  font-family: "Poppins", sans-serif;  
 }  
 body {  
  height: 100vh;  
  background: linear-gradient(to top left, #000000e6, #404040) no-repeat;  
 }  
 /* width */  
 ::-webkit-scrollbar {  
  width: 5px;  
 }  
 /* Track */  
 ::-webkit-scrollbar-track {  
  background: transparent;  
 }  
 /* Handle */  
 ::-webkit-scrollbar-thumb {  
  border-radius: 5rem;  
  background: #d0d0ce45;  
 }  
 /* Handle on hover */  
 ::-webkit-scrollbar-thumb:hover {  
  background: #555;  
 }  
 .header {  
  top: 0;  
  left: 0;  
  width: 100vw;  
  height: 3rem;  
  display: flex;  
  position: fixed;  
  padding-left: 1.5rem;  
  padding-right: 1.5rem;  
  align-items: center;  
  background: #232323;  
  justify-content: space-between;  
 }  
 .header .title {  
  color: #ffffff;  
 }  
 .header .options .inBtn {  
  border: 0;  
  width: 3rem;  
  height: 3rem;  
  font-size: 1.2rem;  
  cursor: pointer;  
  color: #ffffff;  
  margin-right: 0.5rem;  
  background: transparent;  
 }  
 .header .options .inBtn:hover {  
  background-color: #5903d8;  
 }  
 .mainContainer {  
  height: 100%;  
  width: 100vw;  
  display: flex;  
  align-items: center;  
  justify-content: center;  
 }  
 .dragOrDropContainer {  
  width: 80%;  
  height: 80%;  
  display: flex;  
  flex-direction: column;  
  align-items: center;  
  justify-content: center;  
 }  
 .dragOrDropContainer.hide {  
  display: none;  
 }  
 .dragOrDropContainer .icon {  
  height: 6.5rem;  
  font-size: 5rem;  
  color: #ffffff;  
 }  
 .dragOrDropContainer .text {  
  color: #ffffff;  
  text-align: center;  
  font-size: clamp(1rem, 8vw, 2rem);  
 }  
 .dragOrDropContainer.drag .icon {  
  animation: upload 1s infinite linear alternate;  
 }  
 @keyframes upload {  
  0% {  
   transform: translateY(0rem);  
  }  
  100% {  
   transform: translateY(-1rem);  
  }  
 }  
 .editorOptions .sideBar {  
  top: 4rem;  
  width: 15rem;  
  height: 80vh;  
  right: -15rem;  
  position: fixed;  
  overflow-y: hidden;  
  background: #181818;  
  border-top-left-radius: 0.5rem;  
  border-bottom-left-radius: 0.5rem;  
  transition: right 0.5s cubic-bezier(0, 0.81, 0, 0.95);  
 }  
 .editorOptions .sideBar.active {  
  right: 0;  
 }  
 .editorOptions .sideBar.active .sidebarToggleBtn {  
  right: 15rem;  
 }  
 .editorOptions .sideBar:hover {  
  overflow-y: auto;  
 }  
 .editorOptions .sideBar .preview {  
  padding-top: 0.6rem;  
  padding-left: 1rem;  
  padding-bottom: 0.5rem;  
  border-bottom: 1px solid #ffffff2b;  
 }  
 .editorOptions .sideBar .preview p {  
  color: #ffffffc7;  
 }  
 .editorOptions .sideBar .previewImage {  
  width: 90%;  
  height: 8rem;  
  overflow: hidden;  
  border-radius: 0.2rem;  
  border: 2px dashed #ffffff4a;  
 }  
 .editorOptions .sideBar .options .zoom,  
 .editorOptions .sideBar .options .flip,  
 .editorOptions .sideBar .options .aspectRatio,  
 .editorOptions .sideBar .options .dragMode,  
 .editorOptions .sideBar .options .controlCropper,  
 .editorOptions .sideBar .options .rotate {  
  padding-bottom: 0.6rem;  
  border-bottom: 1px solid #ffffff14;  
 }  
 .editorOptions .sideBar .options .zoom p,  
 .editorOptions .sideBar .options .flip p,  
 .editorOptions .sideBar .options .dragMode p,  
 .editorOptions .sideBar .options .aspectRatio p,  
 .editorOptions .sideBar .options .controlCropper p,  
 .editorOptions .sideBar .options .rotate p {  
  font-size: 0.8rem;  
  padding-top: 0.6rem;  
  padding-left: 1rem;  
  color: #ffffffc7;  
 }  
 .optionBtn,  
 .optionBtnSqr {  
  border: 0;  
  width: 2.5rem;  
  height: 2.5rem;  
  cursor: pointer;  
  font-size: 1rem;  
  border-radius: 50%;  
  color: #ffffffc7;  
  margin-right: 1rem;  
  background: #ffffff0f;  
  transition: all 0.2s ease-in-out;  
 }  
 .optionBtnSqr {  
  width: 3rem;  
  margin-right: 0.5rem;  
  margin-bottom: 0.5rem;  
  height: 2rem !important;  
  font-size: 0.9rem !important;  
  border-radius: 0.5rem !important;  
 }  
 .optionBtnSqr:hover,  
 .optionBtnSqr.selected {  
  background: #ffffff1f;  
 }  
 .optionBtn:hover,  
 .optionBtn.selected {  
  background: #ffffff1f;  
 }  
 .btnWrapper {  
  width: 100%;  
  margin-top: 0.5rem;  
  padding-left: 1rem;  
 }  
 .sidebarToggleBtn {  
  right: 0;  
  top: 5rem;  
  border: 0;  
  width: 2rem;  
  height: 2.5rem;  
  cursor: pointer;  
  position: fixed;  
  color: #ffffff;  
  background: #0000004d;  
  border-top-left-radius: 0.5rem;  
  border-bottom-left-radius: 0.5rem;  
  transition: right 0.5s cubic-bezier(0, 0.81, 0, 0.95);  
 }  
 #imageWorkSpace {  
  max-width: 100%;  
  max-height: 100%;  
 }  
 .cropper-drag-box {  
  opacity: 1!important;  
  background-color: #00000080!important;  
 }  
 .outputDownloadBtn.hide {  
  display: none;  
 }  
Enter fullscreen mode Exit fullscreen mode

JavaScript CODE

// --------------------- Created By InCoder ---------------------  
 const openNewFile = document.querySelector('.openNewFile')  
 dragOrDropContainer = document.querySelector('.dragOrDropContainer')  
 dragBoxText = document.querySelector('.dragOrDropContainer .text')  
 fileInput = document.querySelector('.header .options input[type=file]')  
 sidebarToggleBtn = document.querySelector('.sidebarToggleBtn')  
 sideBar = document.querySelector('.sideBar')  
 outputDownloadBtn = document.querySelector('.outputDownloadBtn')  
 optionBtnSqr = document.querySelectorAll('.optionBtnSqr')  
 dragModeBtn = document.querySelectorAll('.dragModeBtn')  
 let file  
 openNewFile.addEventListener("click", () => {  
   fileInput.click()  
 })  
 fileInput.addEventListener('change', () => {  
   file = fileInput.files[0];  
   uploadFile()  
 })  
 const uploadFile = () => {  
   let fileType = file.type;  
   dragOrDropContainer.style.cursor = 'progress'  
   dragBoxText.innerText = 'Uploading file, Please Wait...'  
   let validExt = ["image/jpeg", "image/jpg", "image/png"];  
   if (validExt.includes(fileType)) {  
     let p = new Promise((resolve, reject) => {  
       let fileReader = new FileReader()  
       fileReader.onload = () => {  
         let fileURL = fileReader.result  
         let imageTag = `<img src="${fileURL}" id="imageWorkSpace" alt="image">`  
         dragOrDropContainer.innerHTML = imageTag  
         dragOrDropContainer.style.cursor = 'auto'  
         outputDownloadBtn.classList.remove('hide')  
         resolve(true)  
       }  
       fileReader.readAsDataURL(file)  
     }).then(() => {  
       let options = {  
         dargMode: "move",  
         preview: ".previewImage",  
         viewMode: 2,  
         modal: false,  
         background: false,  
         ready: () => {  
           // Zoom Image  
           document.querySelector('.zoomIn').onclick = () => cropper.zoom(0.1)  
           document.querySelector('.zoomOut').onclick = () => cropper.zoom(-0.1)  
           // Rotate Image  
           document.querySelector('.rotateLeft').onclick = () => cropper.rotate(90)  
           document.querySelector('.rotateRight').onclick = () => cropper.rotate(-90)  
           // Flip Image  
           let flipX = -1  
           flipY = -1  
           document.querySelector('.flipLeftRight').onclick = () => {  
             cropper.scale(flipX, 1)  
             flipX = -flipX  
           }  
           document.querySelector('.flipUpDown').onclick = () => {  
             cropper.scale(1, flipY)  
             flipY = -flipY  
           }  
           // set Aspect Ratio  
           optionBtnSqr[0].onclick = () => cropper.setAspectRatio(1.7777777777777777)  
           optionBtnSqr[1].onclick = () => cropper.setAspectRatio(1.4444444444444444)  
           optionBtnSqr[2].onclick = () => cropper.setAspectRatio(1)  
           optionBtnSqr[3].onclick = () => cropper.setAspectRatio(0.6666666666666666)  
           optionBtnSqr[4].onclick = () => cropper.setAspectRatio(0)  
           // Cropper Control  
           document.querySelector('.cropperClear').onclick = () => cropper.clear()  
           document.querySelector('.cropperCrop').onclick = () => cropper.crop()  
           // Drag Mode  
           dragModeBtn[0].onclick = () => {  
             dragModeBtn[0].classList.remove('selected')  
             dragModeBtn[0].classList.toggle('selected')  
             cropper.setDragMode("crop")  
           }  
           dragModeBtn[1].onclick = () => {  
             dragModeBtn[0].classList.remove('selected')  
             dragModeBtn[1].classList.toggle('selected')  
             cropper.setDragMode("move")  
           }  
           // download Image  
           outputDownloadBtn.onclick = () => {  
             outputDownloadBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>'  
             setTimeout(() => {  
               cropper.getCroppedCanvas().toBlob((blob) => {  
                 let downloadURL = window.URL.createObjectURL(blob)  
                 let a = document.createElement('a')  
                 a.href = downloadURL  
                 a.download = `output-${Date.now()}.jpg`  
                 a.click()  
                 outputDownloadBtn.innerHTML = '<i class="fa-solid fa-download"></i>'   
               })  
             }, 2000)  
           }  
         }  
       }  
       let imageWorkSpace = document.querySelector('.dragOrDropContainer #imageWorkSpace')  
       let cropper = new Cropper(imageWorkSpace, options)  
     })  
   } else {  
     dragOrDropContainer.classList.remove("hide")  
     dragOrDropContainer.classList.remove("drag")  
     dragBoxText.innerText = "Drag & Drop to Upload File"  
     alert("This File is nat valid. Please choose another file and try again.")  
   }  
 }  
 sidebarToggleBtn.addEventListener("click", () => {  
   sideBar.classList.toggle('active')  
   if (sideBar.classList.contains('active')) {  
     sidebarToggleBtn.querySelector('i').classList.remove('fa-chevron-left')  
     sidebarToggleBtn.querySelector('i').classList.add('fa-chevron-right')  
   } else {  
     sidebarToggleBtn.querySelector('i').classList.remove('fa-chevron-right')  
     sidebarToggleBtn.querySelector('i').classList.add('fa-chevron-left')  
   }  
 })  
 dragOrDropContainer.addEventListener('dragover', (e) => {  
   e.preventDefault()  
   dragOrDropContainer.classList.add('drag')  
   dragBoxText.innerText = 'Release to Upload File'  
 })  
 dragOrDropContainer.addEventListener('dragleave', (e) => {  
   e.preventDefault()  
   dragOrDropContainer.classList.remove('drag')  
   dragBoxText.innerText = 'Drag & Drop to Upload File'  
 })  
 dragOrDropContainer.addEventListener('drop', (e) => {  
   e.preventDefault()  
   file = e.dataTransfer.files[0];  
   uploadFile()  
 })  
Enter fullscreen mode Exit fullscreen mode

Top comments (0)