This article about the picture upload, mainly for the upload profile picture. As you all know, there are four steps to uploading an avatar:

Select Image -> Preview Image -> Crop image -> Upload image

Next, detailed implementation of each step.

Choose picture

What’s so good about choosing pictures? Input [type=file] input[type=file] That’s true, but to make things more user-friendly, such as filtering out non-image files, or allowing only images taken from the camera, some simple configurations are needed.

Here’s a look at the simplest selection:

<input type="file" />
Copy the code

At this point, click on the input, and the display on the iOS phone looks like this:

The “Browse” option allows you to view files that are not image types, which is not what we want because we only want image types. This can be done with the Accept attribute as follows:

  <input type="file" accept="image/*">
Copy the code

This filters out non-image types. However, there may be too many image types, some of which may not be supported by the server, so if you want to be conservative and only allow JPG and PNG types, you can write this:

<input type="file" accept="image/jpg, image/jpeg, image/png">
Copy the code

Or:

<input type="file" accept=".jpg, .jpeg, .png">
Copy the code

OK, the need to filter non-images is done. However, in some cases, the product requires that the photo be collected only from the camera. For example, you need to upload the id photo, which prevents you from looking for someone else’s ID to upload from the Internet. Then the Capture attribute can be used:

  <input type="file" accept="image/*" capture>
Copy the code

At this point, you can’t select the photo from the file system, but only from the camera. At this point, it may feel perfect, but there is a problem. There may be some perverse products that require the front-facing camera to be turned on by default to capture pictures, such as your selfies. By default, Capture calls the rear camera. Capture =”user” is enabled by default on the front camera, as follows:

<input type="file" accept="image/*" capture="user">
Copy the code

Ok, so much for choosing images, one caveat is that some configurations may have compatibility issues, so you need to test them on different models to see how they work.

Let’s talk about the implementation of the preview image again.

The preview image

In ancient times, there was no way to preview images on the front end. At that time, the user selects the image, immediately uploads the image to the server, and then the server returns the URL of the remote image to the front-end display. This is a bit cumbersome and will waste your user’s traffic because you may have already uploaded it before the user has decided to. Fortunately, the old days are long gone, and modern browsers have implemented the ability to preview images on the front end. Two common methods are url.createObjecturl () and FileReader. Although they are currently in the Working Draft phase of the W3C specification, most modern browsers are already well supported. Here’s how to use these two methods.

1. Preview using url.createObjECturl

The static url.createObjECturl () method creates a DOMString that contains a URL representing the object given in the argument. The lifecycle of the URL and the Document binding in the window that created it. This new URL object represents the specified File object or Blob object. Usage:

objectURL = URL.createObjectURL(object);

The object parameter refers to the File object, Blob object, or MediaSource object used to create the URL.

For our input[type=file], input.files[0] can get the file object of the currently selected file. The example code is as follows:

  <input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="Picture Preview">
  <script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change'.function() {
      const file = this.files[0];
      $previewImage.src = file ? URL.createObjectURL(file) : ' ';
    }, this);
  </script>
Copy the code

For details, see url.createObjecturl () on MDN,

2. Preview using FileReader

A FileReader object allows a Web application to asynchronously read the contents of a File (or raw data buffer) stored on a user’s computer, using a File or Blob object to specify which File or data to read. Similarly, we can use input.files[0] to get the File object of the currently selected image.

Note in particular that FileReader and is asynchronously read files or data!

Here is an example of previewing an image using FileReader:

<input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="Picture Preview">
  <script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change'.function() {
      const file = this.files[0];
      const reader = new FileReader();
      reader.addEventListener('load'.function() {
        $previewImage.src = reader.result;
      }, false);
      
      if(file) { reader.readAsDataURL(file); }},false)
  </script>
Copy the code

You’ll find that FileReader is a little more complex.

For more information on how to use FileReader, see the MDN document FileReader

A comparison of the two methods

My personal preference is to use url.createObjecturl (). The main thing is that its API is concise, it reads synchronously, and it returns a URL, which is much leaner than FileReaer returns base64. In terms of compatibility, both are similar, and both are at the WD stage. For performance comparison, on Chrome, select a 2M image, url.createObjecturl () takes 0, while FileReader takes about 20ms. This method returns a URL immediately, but MY guess is that the content specified in the URL has not been generated yet. It should be generated asynchronously and rendered. So there’s no good way to compare their performance.

If you want to learn more about image previews, read these two articles:

  • Use FileReader to preview front end images
  • Js Image/Video preview – url.createObjecturl ()

Cut out pictures

As for image cropping, it is natural to think of using canvas, which is true. However, if we do it all by ourselves, we may need to do more work. Therefore, in order to save effort, we can stand on the shoulders of giants. A good image cropping library is Cropperjs, which allows you to scale, move, and rotate images.

The detailed configuration of Cropperjs will not be expanded here, you can see the documentation for yourself. Below we will be based on this library, to achieve a face cropping example:

<input id="inputFile" type="file" accept="image/*">
  <img class="preview-image" id="previewImage" src="" alt=""> <! <div class="cropper" id="cropper">
    <div class="inner">
      <div class="face-container">
        <img class="cropper-image" id="cropperImage">
      </div>
      <div class="tips"> <div class="toolbar">
        <div class="btn" id="confirm""> </div> </div> </div> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
 
 <style>
  .preview-image,
  .cropper-image {
    max-width: 100%;
  }

  .cropper {
    display: none;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: #ccc;The font - size: 0.27 rem; text-align: center; } .inner { height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; } .face-container { position: relative; width: 320px; height: 320px; margin: 50px auto; } .cropper-modal { background: url('https://ok.166.net/gameyw-misc/opd/squash/20191028/152551-m37snfsyu1.png') center no-repeat;
      background-size: 100% 100%;
      opacity: 1;
    }

    .cropper-bg {
      background: none;
    }

    .cropper-view-box {
      opacity: 0;
    }

    .tips {
      font-size: 16px;
    }

    .toolbar {
      display: flex;
      justify-content: center;
      margin: 50px 0;
    }

    .btn {
      width: 150px;
      line-height: 40px;
      font-size: 20px;
      text-align: center;
      color: #fff;
      background: #007fff;
    }
  </style>

  <script>
  const $ = document.getElementById.bind(document);
  const $cropper = $('cropper');
  const $inputFile = $('inputFile');
  const $previewImage = $('previewImage');
  const $cropperImage = $('cropperImage');
  const $confirmBtn = $('confirm')
  letcropperInstance = null; // After selecting the picture, the picture cropping box is displayed$inputFile.addEventListener('change'.function() {
    const file = this.files[0];
    if(! file)return;
    $cropperImage.src = URL.createObjectURL(file);
    showCropper();
  }, false); // Click the ok button to display the cropped image in the IMG tag.$confirmBtn.addEventListener('click'.function() {
    const url = cropperInstance.getCroppedCanvas().toDataURL("image/jpeg", 1.0);
    $cropper.style.display = 'none';
    $previewImage.src = url;
  }, false);


  function showCropper() {
    $cropper.style.display = 'block';
    cropperInstance && cropperInstance.destroy();
    cropperInstance = new Cropper($cropperImage, {
      viewMode: 1,
      aspectRatio: 1,
      autoCropArea: 1,
      dragMode: 'move',
      guides: false,
      highlight: false,
      cropBoxMovable: false,
      cropBoxResizable: false
    });
  }
  </script>
Copy the code

The renderings are as follows:

upload

The previous operation has completed the preparation before uploading the image, including selecting the image, previewing the image, editing the image, and so on, then you can upload the image. The example above, using the cropperInstance. GetCroppedCanvas () method to get to the corresponding canvas object. The canvas object is easy because the Canvas.toblob () method gets the corresponding Blob object, and then we can add the Blob object to FromData to commit without refreshing. The code looks like this:

function uploadFile() {
    cropperInstance.getCroppedCanvas().toBlob(function(blob) {
      const formData = new FormData();
      formData.append('avatar', blob);
      fetch('xxxx', {
        method: 'POST',
        body: formData
      });
    });
  }
Copy the code

This code doesn’t actually execute because we don’t have a corresponding back-end server yet. If you want to try uploading pictures, you can refer to this article for beginners to the front of the various file upload guide, from small pictures to large files to resume, due to the length of the reason, here will not expand.

Afterword.

The introduction to picture uploading is almost over. However, I encountered a strange problem on my iPhone and Xiaomi phones: when I used the front-facing camera to take a selfie, it would rotate 90 degrees counterclockwise after I selected it, such as the picture below:

Why is the preview turned sideways when the photo was taken in front of you? When I first encountered this problem, I thought it was strange. Later, I checked and found that this was because the camera always records the Angle information when taking photos. Maybe the Angle information recorded by the iPhone’s front camera is a little different from other ones, and the iPhone’s own album automatically corrected the Angle when viewing photos, while the browser did not, so this rotation occurred.

To solve this problem, you need to use the EXIF library.

I just had a try and found that my iPhone does not have this problem now. It was about half a year ago, when I was making a demand, this kind of rotation occurred in the selfie pictures. It is possible that this problem has been fixed after the iOS system was upgraded. And now there is no Xiaomi phone around, so it is not good to reproduce. Fortunately, I saved an image that rotated automatically. You can download it here:

Ok.166.net/gameyw-misc…

After the image is downloaded, it works fine to open it in your computer’s image viewer, but it rotates when you select the image in your browser and preview it using url.createObjecturl () or FileReader. Even a direct img tag is rotated 90 degrees counterclockwise, as in:

<img src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png">
Copy the code

The effect is as follows:

Here is an example of how to use EXIF to detect the Angle of an image. For more information on how to use EXIF, you can check out the Github homepage github.com/exif-js/exi…

  <img id="exifImage" src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png" alt="">

  <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script>
  <script>
      const $exifImage = document.getElementById('exifImage');
      $exifImage.onload = function() {
        EXIF.getData($exifImage.function() {
          let allMetaData = EXIF.getAllTags(this);
          console.log(allMetaData.Orientation); // 6
        });
      };
  </script>
Copy the code

“Allmetadata. Orientation” : 6 Check out the chart in the Exif Orientation Tag:

If you don’t understand this chart, check out this image in JPEG Orientation:

As you can see, the camera information is rotated 90 degrees counterclockwise. So how do you correct that? You can use CSS transfrom: Rotate (-90deg) to rotate 90 degrees clockwise to cancel out this Angle.

In fact, CropperJS will also detect EXIF information and automatically correct the Angle, see github.com/fengyuanche… It is also mentioned here, but it only supports EXIF information for JPG images, which is not supported in our PNG image.

There’s a CSS property called image-orientation, which has a value called from-image, which is rotated using the EXIF data of the image. Unfortunately, chrome does not currently support this property. Interested can learn about it.

Well, I’ll stop here, if you have any questions, please feel free to communicate in the comments section

PS: I have not updated the article for more than half a year. Set a small goal and update it every two weeks.