The cropped image is sent to JSONPlaceholder, a third-party fake API. It does not store uploaded files, and requests may fail.

<img id="image" src="/cropperjs/picture.jpg" alt="Image to crop">
<input type="file" id="input" accept="image/*">
<button type="button" id="upload">Crop and upload</button>
<progress id="progress" max="100" value="0" hidden></progress>
<p id="status" role="status" aria-live="polite"></p>
<script src="https://unpkg.com/cropperjs@2/dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
const image = document.getElementById('image');
const input = document.getElementById('input');
const avatar = document.getElementById('avatar');
const uploadButton = document.getElementById('upload');
const progress = document.getElementById('progress');
const status = document.getElementById('status');
const template = `<cropper-canvas background>
<cropper-image rotatable scalable skewable translatable></cropper-image>
<cropper-shade hidden></cropper-shade>
<cropper-handle action="select" plain></cropper-handle>
<cropper-selection initial-coverage="0.5" aspect-ratio="1" movable resizable>
<cropper-grid role="grid" bordered covered></cropper-grid>
<cropper-crosshair centered></cropper-crosshair>
<cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
<cropper-handle action="n-resize"></cropper-handle>
<cropper-handle action="e-resize"></cropper-handle>
<cropper-handle action="s-resize"></cropper-handle>
<cropper-handle action="w-resize"></cropper-handle>
<cropper-handle action="ne-resize"></cropper-handle>
<cropper-handle action="nw-resize"></cropper-handle>
<cropper-handle action="se-resize"></cropper-handle>
<cropper-handle action="sw-resize"></cropper-handle>
</cropper-selection>
</cropper-canvas>`;
const cropper = Cropper.createCropper(image, { template: template });
let objectURL;
input.addEventListener('change', function () {
const file = input.files && input.files[0];
if (!file) {
return;
}
if (objectURL) {
URL.revokeObjectURL(objectURL);
}
objectURL = URL.createObjectURL(file);
cropper.getCropperImage().src = objectURL;
status.textContent = '';
input.value = '';
});
uploadButton.addEventListener('click', async function () {
const selection = cropper.getCropperSelection();
if (!selection) {
return;
}
const previousAvatarURL = avatar.src;
try {
const canvas = await selection.$toCanvas({ width: 160, height: 160 });
const blob = await new Promise(function (resolve) {
canvas.toBlob(resolve, 'image/jpeg');
});
if (!blob) {
throw new Error('Unable to create an image blob.');
}
avatar.src = canvas.toDataURL('image/jpeg');
progress.value = 0;
progress.hidden = false;
uploadButton.disabled = true;
status.textContent = 'Uploading...';
await new Promise(function (resolve, reject) {
const formData = new FormData();
const request = new XMLHttpRequest();
formData.append('avatar', blob, 'avatar.jpg');
request.open('POST', 'https://jsonplaceholder.typicode.com/posts');
request.upload.addEventListener('progress', function (event) {
if (event.lengthComputable) {
progress.value = Math.round((event.loaded / event.total) * 100);
}
});
request.addEventListener('load', function () {
if (request.status >= 200 && request.status < 300) {
resolve();
} else {
reject(new Error('Upload failed.'));
}
});
request.addEventListener('error', function () {
reject(new Error('Upload failed.'));
});
request.send(formData);
});
status.textContent = 'Upload success';
} catch (error) {
avatar.src = previousAvatarURL;
status.textContent = error.message || 'Upload error';
} finally {
progress.hidden = true;
uploadButton.disabled = false;
}
});
});
</script>