Skip to content

Instantly share code, notes, and snippets.

@Qubadi
Last active April 7, 2025 16:47
Show Gist options
  • Save Qubadi/b918070aaf15f1e48b0b2ad9baeeb117 to your computer and use it in GitHub Desktop.
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>
@Ferryvb
Copy link

Ferryvb commented Nov 3, 2024

Hi,

Can this script be made to force users to crop the image? Where it will automatically open the cropper window so they get forced to preform the crop and then only use the cropped image as input for the upload field?

Removing 2 of the options still makes it possible to not crop the image.

Best case would be:
certain size crop. Not selectable by user, so they can only zoom and move the crop box to their best fit.
Field requirement* accepted by cropped image, not the original one. So all images upload become the same size and aspect ratio.

Thanks

@Karbi-Prathul
Copy link

Crop is enable only when we are uploading new image. Its not enabled when loading image from preset value.
How to make it enable always.?

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