本章内容主要讲述上传头像的4个步骤:

       选择图片 --》 预览图片 --》 裁剪图片 --》 上传图片

选择图片

首先先看看最简单的选择图片:

<input type="file" />

但是我们只需要图片类型的文件,这并不是我们想要的结果。可以通过accept属性实现,如下:

<input type="file" accept="image/*" />

这样就可以过滤掉非图片类型了。但是图片的类型可能也太多了,有些可能服务器不支持,所以,如果想保守一些,只允许jpg、jpeg和png类型,也可以写成这样:

<input type="file" accept="image/jpg, image/jpeg, image/png" />
<!--或者 -->
<input type="file" accept=".jpg, .jpeg, .png" />

OK,过滤非图片的需求搞定了。但是有时候,产品还要求只能从摄像头采集图片,可能觉得很完美了,但是还有个问题,可能有些变态产品要求默认打开前置摄像头采集图片,比如就是想要你的自拍照片。capture默认调用的是后置摄像头。默认启用前置摄像头可以设置capture="user",如下:

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

需要注意的是,可能有些配置在兼容性上会有一些问题,所以需要在不同的机型上测试一下看看效果。

预览图片

在很久之前,前端并没有预览图片的方法。当时的做法是,用户选择图片之后,立刻把图片上传到服务器,然后服务器返回远程图片的url给前端显示。这种方法略显麻烦,

而且还会浪费用户的流量,因为用户可能还没有确定要上传,你却已经上传了。幸好,现代浏览器已经实现了前端预览图片的功能。常用的方法有两个,分别是URL.createObjectURL()和FileReader。

虽然它们目前均处于W3C规范中的Working Draft 阶段,但是大多数的现代浏览器都已经良好的支持了。下面就介绍一下如何使用这两个方法。

  • 使用URL.createObjectURL预览

        URL.createObjectURL()静态方法会创建一个DOMString,其中包含一个表示参数中给出的对象的URL。这个URL的生命周期和创建它的窗口中的document绑定。这个新的URL对象表示指定的File对象或Blob对象。用法如下:

objectURL = URL.createObjectURL(object);

   其中, object参数指用于创建URL的File对象、Blob对象或者MediaSource对象 。对于我们的input[type=file]而言,input.files[0]可以获取到当前选中文件的File对象。示例代码如下:

<input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="图片预览">
  <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>


  • 使用FileReader预览

        FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用File或Blob对象指定要读取的文件或数据。同理的,我们也可以通过input.files[0]获取到当前选中的图片的File对象。

特别注意 :FileReader是异步读取文件或数据的!

下面使用FileReader预览图片的示列:

<input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="图片预览">
  <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>

具体用法参照:FileReader

裁剪图片

关于图片的裁剪,很自然的会想到使用canvas,确实是要通过canvas,但是如果全部我们自己来实现,可能需要做比较多的工作,所以为了省力,我们可以站在巨人的肩膀上。比较优秀的图片裁剪库cropperjs,该库可以对图片进行缩放、移动和旋转。

<input id="inputFile" type="file" accept="image/*">
  <img class="preview-image" id="previewImage" src="" alt="">
  <!-- cropper裁剪框 -->
  <div class="cropper" id="cropper">
    <div class="inner">
      <div class="face-container">
        <img class="cropper-image" id="cropperImage">
      </div>
      <div class="tips">请将面部区域置于人脸框架内</div>
      <div class="toolbar">
        <div class="btn" id="confirm">确认</div>
      </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;
    font-size: 0.27rem;
    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')
  let cropperInstance = null;

  // 选择图片后,显示图片裁剪框
  $inputFile.addEventListener('change', function() {
    const file = this.files[0];
    if(!file) return;
    $cropperImage.src = URL.createObjectURL(file);
    showCropper();
  }, false);

  // 点击确认按钮,将裁剪好的图片放到 img 标签显示。
  $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>

上传

上面的例子中,使用了 cropperInstance.getCroppedCanvas() 方法来获取到对应的 canvas 对象 。有了 canvas 对象就好办了,因为 canvas.toBlob() 方法可以取得相应的 Blob 对象,然后,我们就可以把这个 Blob 对象添加到 FromData 进行无刷新的提交了

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