Upload a cropped image to a server

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

Avatar

Avatar preview

Image to crop

Source Code

<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>