Skip to content

Instantly share code, notes, and snippets.

@Qubadi
Last active September 25, 2025 06:55
Show Gist options
  • Select an option

  • Save Qubadi/b918070aaf15f1e48b0b2ad9baeeb117 to your computer and use it in GitHub Desktop.

Select an option

Save Qubadi/b918070aaf15f1e48b0b2ad9baeeb117 to your computer and use it in GitHub Desktop.
JetFormBuilder that allows users to crop images with some great new functionality
UPDATED: 29.09.2024
Copy the following HTML code and create a HTML snippet using your snippet plugins.
Paste the code into the plugin and save it.
I’ve developed a new custom feature for JetFormBuilder that allows users to crop images with some great new functionality.
Here’s a quick rundown of what it does:
JetFormBuilder integration: This custom code integrates seamlessly with JetFormBuilder’s file upload system, making the image
inputs interactive and ready for cropping, all through a custom action hook.
CropperJS integration: It loads the necessary CSS and JavaScript from CropperJS, which is the library responsible for handling
the image cropping functionality.
Crop modal: A simple modal window is included that lets users crop their images, offering zoom controls, size options, and a
save button.
Zoom controls: Users can zoom in and out on the image while cropping, thanks to added event listeners on the buttons.
Aspect ratio selection: There’s a dropdown menu that allows users to pick different crop sizes (aspect ratios),
and the cropper adjusts automatically.
Saving cropped images: When users click "Save Cropped Image," the selected area of the image is saved, turned into a blob,
and replaced in the file input with a unique name, like cropped_image_${Date.now()}.png.
Updating the file input: After cropping, the input field is automatically updated so that the cropped image is used in the
form submission instead of the original.
Re-crop option: If the user changes their mind and wants to adjust the crop, they can simply reopen the modal with the original
image loaded and make new adjustments.
___________________________________________
<!-- Include CropperJS library -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.12/cropper.min.js"></script>
<style>
.jet-form-builder-file-upload__files {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: center;
align-items: center;
}
/* Styles for Crop Image Modal */
.cropModal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background: white;
border-radius: 8px;
border: 1px solid #ccc;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
display: none;
text-align: center;
padding: 20px;
max-width: 90%;
max-height: 90%;
overflow: auto;
}
.cropModal h2 {
margin-top: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: 600;
color: #333;
font-size: 14px !important;
}
.cropModal button,
.cropModal select {
padding: 10px 15px;
margin: 5px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
cursor: pointer;
font-size: 14px !important;
}
.cropModal button:hover,
.cropModal select:hover {
background-color: #0056b3 !important;
}
.imageToCrop {
max-width: 100%;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.cropOptions {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.cropOptions button,
.cropOptions select {
flex: 1 1 auto;
margin: 5px;
}
.crop-button {
padding: 5px 10px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: #ffffff !important;
cursor: pointer;
font-size: 12px !important;
display: inline-block;
width: 100px;
transition: background-color 0.3s ease, color 0.3s ease;
margin: 10px 0 0 0 !important;
word-wrap: break-word;
white-space: normal;
}
.crop-button.cropped {
background-color: #28a745;
position: relative;
}
.crop-button.cropped::after {
content: 'Recrop Image';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
background-color: #0830d2 !important;
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 6px !important;
}
.crop-button.cropped:hover::after {
opacity: 1;
}
.jet-form-builder-file-upload__file.has-crop-button {
margin: 0 10px 50px 0 !important;
}
.jet-form-builder-file-upload__file {
margin: 0 10px 10px 0 !important;
}
</style>
<!-- Crop Image Modal -->
<div id="cropModal" class="cropModal">
<div>
<h2>Crop Image</h2>
<div>
<img id="imageToCrop" class="imageToCrop" />
</div>
<div id="cropOptions" class="cropOptions" style="margin-top: 20px;">
<button id="cropZoomIn">Zoom In</button>
<button id="cropZoomOut">Zoom Out</button>
<select id="cropSize">
<option value="300x300">300x300</option>
<option value="500x500">500x500</option>
<option value="1000x1200">1000x1200</option>
</select>
<button id="saveSelectedImage">Save Cropped Image</button>
<button id="cropCancel">Cancel</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const { addAction } = window.JetPlugins.hooks;
let cropper;
let originalImageData = {};
let filesList = {};
let selectedFileIndex = 0;
let currentUploadField = null;
const cropModal = document.getElementById('cropModal');
const imageToCrop = document.getElementById('imageToCrop');
const cropZoomIn = document.getElementById('cropZoomIn');
const cropZoomOut = document.getElementById('cropZoomOut');
const cropCancel = document.getElementById('cropCancel');
const cropSize = document.getElementById('cropSize');
const saveSelectedImage = document.getElementById('saveSelectedImage');
cropZoomIn.addEventListener('click', () => cropper.zoom(0.1));
cropZoomOut.addEventListener('click', () => cropper.zoom(-0.1));
cropCancel.addEventListener('click', () => {
cropper.destroy();
cropModal.style.display = 'none';
});
cropSize.addEventListener('change', function () {
const [width, height] = cropSize.value.split('x').map(Number);
cropper.setAspectRatio(width / height);
cropper.setCropBoxData({ width, height });
});
function initCropper() {
const [width, height] = cropSize.value.split('x').map(Number);
cropper = new Cropper(imageToCrop, {
aspectRatio: width / height,
viewMode: 1
});
}
saveSelectedImage.addEventListener('click', function (event) {
event.preventDefault();
const [width, height] = cropSize.value.split('x').map(Number);
const canvas = cropper.getCroppedCanvas({ width, height });
const originalFile = filesList[currentUploadField.id][selectedFileIndex].file;
const originalFileType = originalFile.type;
canvas.toBlob(function (blob) {
const input = currentUploadField;
const dataTransfer = new DataTransfer();
const uniqueName = `cropped_image_${Date.now()}.${originalFileType.split('/')[1]}`;
const croppedFile = new File([blob], uniqueName, { type: originalFileType });
filesList[input.id][selectedFileIndex].file = croppedFile;
filesList[input.id].forEach(fileObj => dataTransfer.items.add(fileObj.file));
input.files = dataTransfer.files;
const imgElement = input.closest('.jet-form-builder-file-upload').querySelector(`.jet-form-builder-file-upload__file[data-index="${selectedFileIndex}"] img`);
imgElement.src = URL.createObjectURL(croppedFile);
const buttonElement = input.closest('.jet-form-builder-file-upload').querySelector(`.jet-form-builder-file-upload__file[data-index="${selectedFileIndex}"] .crop-button`);
buttonElement.classList.add('cropped');
buttonElement.textContent = "Cropped";
cropper.destroy();
cropModal.style.display = 'none';
}, originalFileType);
});
function reCrop(event, input) {
event.preventDefault();
currentUploadField = input;
imageToCrop.src = originalImageData[input.id][selectedFileIndex];
cropModal.style.display = 'block';
if (cropper) cropper.destroy();
initCropper();
}
function handleFileChange(event) {
const input = event.target;
filesList[input.id] = [];
originalImageData[input.id] = [];
const fileContainers = input.closest('.jet-form-builder-file-upload').querySelectorAll('.jet-form-builder-file-upload__file');
fileContainers.forEach((container, index) => {
container.setAttribute('data-index', index);
const file = input.files[index];
filesList[input.id].push({ file, dataURL: URL.createObjectURL(file) });
const reader = new FileReader();
reader.onload = e => originalImageData[input.id][index] = e.target.result;
reader.readAsDataURL(file);
let cropButton = container.querySelector('.crop-button');
if (!cropButton) {
cropButton = document.createElement('button');
cropButton.classList.add('crop-button');
container.appendChild(cropButton);
}
cropButton.textContent = "Crop Image";
container.classList.add('has-crop-button');
cropButton.addEventListener('click', function (e) {
e.preventDefault();
selectedFileIndex = index;
reCrop(e, input);
});
});
}
addAction('jet.fb.input.makeReactive', 'crop-image', function (input) {
if (!input?.nodes?.length || !input.nodes[0].classList.contains('jet-form-builder-file-upload__input')) return;
input.nodes[0].addEventListener('change', handleFileChange);
});
});
</script>
@sakibstime
Copy link

Thanks it woks fine

@Joe-Bloggs
Copy link

Legend! That works very well. Thanks so much @Qubadi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment