转载

利用`FileReader`和`FormData`实现图片预览和上传

业务有个需求,要做图片预览上传,过去都是客户端上传给后端,后端返回 url 前端进行预览,现在其实可以不依赖后端做预览,最后在上传,这主要依赖 FileReaderFormData 这两个对象和 JavaScript 处理二进制的能力。

OK,Show code~,以下代码已注释掉具体业务逻辑和实现,如果需要了解 API 细节,可以请参考:

  • https://developer.mozilla.org/en-US/docs/Web/API/FileReader
  • https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
  • https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

监听表单文件变化

文件表单的样式主要通过让它后面,通过别的DOM来美化它。

<input type="file">
input.on.('change', preview);

预览

预览使用 FileReader 对象来读:

function preview(e) {     var file = e.target.files[0];     var reader = new FileReader();      reader.onloadend = function () {         // 图片的 base64 格式, 可以直接当成 img 的 src 属性值         var dataURL = reader.result;         var img = new Image();         img.src = dataURL;         // 插入到 DOM 中预览         // ...     };      reader.readAsDataURL(file); // 读出 base64  }

提交图片文件(二进制文件 非 base64)

base64 转 二进制文件

/**  * dataURL to blob, ref to https://gist.github.com/fupslot/5015897  * @param dataURI  * @returns {Blob}  */ function dataURItoBlob(dataURI) {     var byteString = atob(dataURI.split(',')[1]);     var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];     var ab = new ArrayBuffer(byteString.length);     var ia = new Uint8Array(ab);     for (var i = 0; i < byteString.length; i++) {         ia[i] = byteString.charCodeAt(i);     }     return new Blob([ab], {type: mimeString}); }

构造 FormData 填充二进制文件数据,通过 ajax 的方式进行提交:

var fd = new FormData(); var blob = dataURItoBlob(dataURL); fd.append('file', blob);   $.ajax({     type: 'POST',     url: '/upload',     data: fd,     processData: false, // 不会将 data 参数序列化字符串     contentType: false, // 根据表单 input 提交的数据使用其默认的 contentType     xhr: function() {         var xhr = new window.XMLHttpRequest();         xhr.upload.addEventListener("progress", function(evt) {             if (evt.lengthComputable) {                 var percentComplete = evt.loaded / evt.total;                 console.log('进度', percentComplete);             }         }, false);          return xhr;     } }).success(function (res) {     // 拿到提交的结果 }).error(function (err) {     console.error(err); });

注意:不要漏了指定 processDatacontentTypefalse

压缩

业务中不需要前端不需要压缩,因为后端有更靠谱的压缩方案,但是前端其实也可以压缩,那就是用 canvas 把图画出适合的大小,然后上传。

主要流程:

  • new 出来的 Image 对象,我们监听它的 onload 事件
  • 按照压缩比例,算出压缩后的图片尺寸
  • 创建 canvas ,尺寸设置成上一步骤算出来的压缩后的图片尺寸
  • 调用 drawImage 方法,把图片绘制到 canvas
  • 调用 canvastoDataURL ,取出 base64 格式的数据
  • 后续的传图步骤和上面的原图上传一样
var img = new Image();  img.onload = function () {     // 当图片宽度超过 400px 时, 就压缩成 400px, 高度按比例计算     // 压缩质量可以根据实际情况调整     var w = Math.min(400, img.width);     var h = img.height * (w / img.width);     var canvas = document.createElement('canvas');     var ctx = canvas.getContext('2d');      // 设置 canvas 的宽度和高度     canvas.width = w;     canvas.height = h;      // 把图片绘制到 canvas 中     ctx.drawImage(img, 0, 0, w, h);      // 取出 base64 格式数据     var dataURL = canvas.toDataURL('image/png');      // ... };  img.src = reader.result;

自己的 OneWord 客户端的上传组件就是这么做的: ow-image-uploader.vue

这样一看,好像除去业务逻辑的话,好像也没多少代码

PS:需要注意的是,通过 canvas 绘制的图片,低版本 IOS 会出现比例不正确的情况,请参考 https://github.com/stomita/ios-imagefile-megapixel

原文  https://github.com/lishengzxc/bblog/issues/13
正文到此结束
Loading...