栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > JavaScript

手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件

JavaScript 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件

作者:混沌传奇

原文链接:https://juejin.im/post/5a9759a16fb9a0635b5360b3

前言

今天我们要讲解下如何编写一个图片压缩、方向纠正插件,附带着会讲解下如何上传和预览。

为什么重点放在图片压缩和方向纠正?

相信大家在做项目过程中,经常会遇到上传图片到后端,但是由于图片过大,需要对图片压缩处理。特别在移动端,手机拍的照片普遍过于大了,我们有时候只是需要上传一张头像,很小就够用了。还有在部分手机上(已知苹果手机)拍的照片存在方向角度问题,这时就需要我们来纠正图片角度了。

很多同学多数时候是在用别人写好的图片压缩上传插件。针对我们的需求,这些插件有时候不能达到我们最理想的效果,自己写呢,又不会写,很是头疼。今天就深入剖析讲解下,教会大家编写自己的图片压缩、方向纠正插件,以及预览和上传压缩后图片数据。

文中用到的一些H5的api和EXIF.js等知识点如果不懂的话,请先阅读文末尾的结语中的知识点资料。

实现原理 压缩图片并且上传主要用到filereader、canvas 以及 formdata 这三个h5的api和插件EXIF.js。逻辑并不难。整个过程就是:
  
(1)用户使用input file上传图片的时候,用filereader读取用户上传的图片数据(base64格式)
  
(2)把图片数据传入img对象,然后将img绘制到canvas上,用EXIF.js对图片方向进行纠正,再调用canvas.toDataURL对图片进行压缩,获取到压缩后的base64格式图片数据,转成二进制
  
(3)获取到压缩后的图片二进制数据,预览。
  
(4)将压缩后的图片二进制数据塞入formdata,再通过XmlHttpRequest提交formdata
  
如此四步,就完成了图片的压缩、方向纠正、预览和上传。
插件设计思考

考虑到在实际项目中,可能用不同的开发框架(vue.js/JQ/react.js/angular.js/anu.js等),图片预览的UI样式也可能不同,图片数据上传方法可能不同。因为图片压缩和方向纠正这两块的逻辑多变性比较低,我们这里把图片压缩和方向纠正抽离出来,封装为一个插件库。

【一】获取图片数据

先是获取图片数据,也就是监听input file的change事件,然后获取到用来压缩上传的文件对象files,将files传到【图片压缩、方向纠正插件】中进行处理。

这时候根据每个人的需求,也可以预览未压缩的图片。

//监听上传组件input的onchange事件,压缩图片,纠正图片方向,同时获取压缩后的图片
filechooser.onchange = function () {
    var fileList = this.files;

    //预览压缩前的图片
    var files = Array.prototype.slice.call(fileList);
    files.forEach(function (file, i) {
 var reader = new FileReader();
 reader.onload = function () {
     var li = document.createElement("li")
     li.style.backgroundImage = 'url('+this.result+')';
     document.querySelector('.img_list').appendChild(li)
 }
 reader.readAsDataURL(file);
    });

    //处理图片列表,getCompressiveFileList接受处理后的图片数据列表
    //下面两行代码为图片压缩、方向纠正插件的用法,具体实现细节请继续往下阅读 ~_~ 
    var process = window.lzImgProcess();
    process(fileList, getCompressiveFileList);
}

【二】图片压缩、方向纠正插件实现

上面做完图片数据的获取后,就可以做process压缩图片的方法了。而压缩图片也并不是直接把图片绘制到canvas再调用一下
---- toDataURL就行的。
  在IOS中,canvas绘制图片是有两个限制的:
  首先是图片的大小,如果图片的大小超过两百万像素,图片也是无法绘制到canvas上的,调用drawImage的时候不会报错,但是你用toDataURL获取图片数据的时候获取到的是空的图片数据。
  再者就是canvas的大小有限制,如果canvas的大小大于大概五百万像素(即宽高乘积)的时候,不仅图片画不出来,其他什么东西也都是画不出来的。
  应对上面两种限制,我把图片宽度、高度压缩控制在1000px以内,这样图片最大就不超过两百万像素了。在前端开发中,1000px*1000px基本可以满足绝大部分的需求了。当然了还有更完美的瓦片式绘制的方法,我们这里就说瓦片式绘制方法了。
  如此一来就解决了IOS上的两种限制了。
  除了上面所述的限制,还有两个坑,一个就是canvas的toDataURL是只能压缩jpg的(这句话的详细解释可以看下面的Tip讲解),当用户上传的图片是png的话,就需要转成jpg,也就是统一用canvas.toDataURL('image/jpeg', 0.5) , 类型统一设成jpeg,而压缩比就自己控制了。
  另一个就是如果是png转jpg,绘制到canvas上的时候,canvas存在透明区域的话,当转成jpg的时候透明区域会变成黑色,因为canvas的透明像素默认为rgba(0,0,0,0),所以转成jpg就变成rgba(0,0,0,1)了,也就是透明背景会变成了黑色。解决办法就是绘制之前在canvas上铺一层白色的底色。
  在压缩图片之前,我们判断图片角度,如果图片角度不正确,还需要用EXIF.js把图片角度纠正过来。
  压缩完图片,把base64的图片数据转成二进制数据存储到暂存区中,等待被getBlobList获取使用。

Tip: canvas的toDataURL是只能压缩jpg这句话我可能说的不清楚,我想表达的意思是这个api无论是jpeg还是png,最后导出的时候,都跟jpeg没啥差别了。因为png图片的透明性质在canvas中是无效的,会被canvas添加默认的黑色背景,我在文中讲解的时候,用白色背景处理了,所以最后导出的图片,无论你设置的是png还是jpeg都跟jpeg没啥区别了,因为无法保持png的透明度性质了

(function(window) {

    

    window.lzImgProcess = function () {

 var Orientation = '', //图片方向角
     blobList = [], //压缩后的二进制图片数据列表

     canvas = document.createElement("canvas"), //用于压缩图片(纠正图片方向)的canvas
     ctx = canvas.getContext('2d'),

     file_type = 'image/jpeg', //图片类型
     qlty = 0.5, //图片压缩品质,默认是0.5,可选范围是0-1的数字类型的值,可配置
     imgWH = 1000; //压缩后的图片的最大宽度和高度,默认是1000px,可配置

 
 function process (fileList, getBlobList, quality, WH) {
     blobList = []; //初始化blobList
     // 判断参数fileList的长度是否大于0
     if (!fileList.length){
  console.log('警告:传进方法process的参数fileList长度必须大于零!!!')
  return;
     }

     //如果quality参数有值,则把quality赋值给qlty(图片压缩的品质)
     if(quality)
  qlty = quality;

     //如果WH参数有值,则把WH赋值给imgWH(压缩后的图片的最大宽度和高度)
     if(WH&&WH<1000&&WH>0){
  imgWH = WH;
     }

     // 把传进来的fileList转为数组类型
     var files = Array.prototype.slice.call(fileList);

     files.forEach(function (file, i) {
  if (!//(?:jpeg|png)/i.test(file.type)){
      console.log('警告:图片必须是jpeg||png类型!!!');
      return;
  }
  // file_type = file.type;

  var reader = new FileReader();

  // 获取图片压缩前大小,打印图片压缩前大小
  var size = file.size/1024 > 1024 ? (~~(10*file.size/1024/1024))/10 + "MB" :  ~~(file.size/1024) + "KB";
  // console.log('size:', size)

  reader.onload = function () {
      var img = new Image();
      img.src = this.result;

      // 图片加载完毕之后进行压缩
      if (img.complete) {
   callback();
      } else {
   img.onload = callback;
      }

      function callback() {
   //获取照片方向角属性,用户旋转控制  
   EXIF.getData(img, function() {
// alert(EXIF.pretty(this));
EXIF.getAllTags(this);   
// alert(EXIF.getTag(this, 'Orientation'));
Orientation = EXIF.getTag(this, 'Orientation');  
if(Orientation == ""||Orientation == undefined||Orientation == null){
    Orientation = 1;
}
   });

   //获取压缩后的图片二进制数据
   var data = GetImgCompress(img);

   //将二进制数据塞入到二进制数据列表中
   blobList.push(data);

   //将压缩后的二进制图片数据对象(blob)组成的list通过钩子函数返回出去
   if(blobList.length===files.length){
if(getBlobList)
    getBlobList(blobList);
   }

   img = null;
      }

  };

  reader.readAsDataURL(file);
     })
 }

 
 function GetImgCompress(img){
     if (navigator.userAgent.match(/iphone/i)) {
  //console.log('iphone');
  //如果方向角不为1,都需要进行旋转
  if(Orientation != "" && Orientation != 1){
      switch(Orientation){
   case 6://需要顺时针(向左)90度旋转
rotateImg(img,'left',canvas);
break;
   case 8://需要逆时针(向右)90度旋转
rotateImg(img,'right',canvas);  
break;  
   case 3://需要180度旋转
rotateImg(img,'right',canvas);//转两次  
rotateImg(img,'right',canvas);
break;  
      }
  }else{
      //不做旋转
      rotateImg(img,'no',canvas); 
  }
     }else if (navigator.userAgent.match(/Android/i)) {// 修复android
  if(Orientation != "" && Orientation != 1){
   switch(Orientation){
case 6://需要顺时针(向左)90度旋转
    rotateImg(img,'left',canvas);
    break;
case 8://需要逆时针(向右)90度旋转
    rotateImg(img,'right',canvas);
    break;
case 3://需要180度旋转
    rotateImg(img,'right',canvas);//转两次
    rotateImg(img,'right',canvas);
    break;
   }
      }else{
   //不做旋转
   rotateImg(img,'no',canvas);  
      }
     }else{
  if(Orientation != "" && Orientation != 1){
      switch(Orientation){
   case 6://需要顺时针(向左)90度旋转
rotateImg(img,'left',canvas);
break;
   case 8://需要逆时针(向右)90度旋转
rotateImg(img,'right',canvas);
break;
   case 3://需要180度旋转
rotateImg(img,'right',canvas);//转两次
rotateImg(img,'right',canvas);
break;
      }
  }else{
      //不做旋转
      rotateImg(img,'no',canvas);
  }
     }

     var ndata;

     ndata = canvas.toDataURL(file_type, qlty);

     //打印压缩前后的大小,以及压缩比率
     // var initSize = img.src.length;
     // console.log('压缩前:' + initSize);
     // console.log('压缩后:' + ndata.length, 'base64数据', ndata);
     // console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%");

     //将压缩后的base64数据转为二进制数据
     ndata = dataURItoBlob(ndata);

     //清除canvas画布的宽高
     canvas.width = canvas.height = 0;

     return ndata;
 }

 
 function rotateImg(img, direction,canvas) {    

     //最小与最大旋转方向,图片旋转4次后回到原方向    
     var min_step = 0;
     var max_step = 3;
     if (img == null)return;    
     //img的高度和宽度不能在img元素隐藏后获取,否则会出错    
     var height = img.height;    
     var width = img.width;

     if(width>imgWH || height>imgWH){
  var ratio = ~~(height/width*10)/10;
  if(width>height){
      width = imgWH;
      height = imgWH*ratio;
  }else{
      height = imgWH;
      width = height/ratio;
  }
  img.width = width;
  img.height = height;
     }

     canvas.width = width;    
     canvas.height = height;   

     // 铺底色
     ctx.fillStyle = "#fff";
     ctx.fillRect(0, 0, width, height);

     var step = 2;    
     if (step == null) {    
  step = min_step;    
     }
     if (direction == 'no'){
  step = 0;    
     } else if (direction == 'right') {    
  step++;    
  //旋转到原位置,即超过最大值    
  step > max_step && (step = min_step);    
     } else {    
  step--;    
  step < min_step && (step = max_step);    
     }

     //旋转角度以弧度值为参数    
     var degree = step * 90 * Math.PI / 180; 

     switch (step) {
  case 0:
      ctx.drawImage(img, 0, 0,width,height);   
      break;    
  case 1:
      ctx.rotate(degree);    
      ctx.drawImage(img, 0, -height,width,height);    
      break;
  case 2:
      ctx.rotate(degree);    
      ctx.drawImage(img, -width, -height,width,height);    
      break;    
  case 3:
      ctx.rotate(degree);    
      ctx.drawImage(img, -width, 0,width,height);    
      break;
     }
 }

 
 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});
 }

 
 return process;

    }
})(window)

Exif.js 提供了 Javascript 读取图像的原始数据的功能扩展,例如:拍照方向、相机设备型号、拍摄时间、ISO 感光度、GPS 地理位置等数据。

Exif.js官方github 仓库地址:github.com/exif-js/exi…

【三】获取压缩后的图片二进制数据,预览图片

完成图片压缩后,就可以获取压缩后的图片二进制数据了,把获取到的图片二进制数据存起来;获取到数据后,可以拿来预览。
由于实际项目中,每个项目的UI样式设计可能不一样,开发者可以根据自己的UI样式来预览图片。

//获取压缩后的图片
function getCompressiveFileList(fileList) {
    blobFileList = fileList;
    // console.log('fileBlobList:', fileList);
    fileList.forEach(function (blob) {
 var reader = new FileReader();
 reader.onload = function () {
     var li = document.createElement("LI")
     li.style.backgroundImage = 'url('+this.result+')';
     document.querySelector('.imgCompress_list').appendChild(li)
 }
 reader.readAsDataURL(blob);
    })
}

【四】提交图片数据到后台

new一个formdata对象,将上一步获取到的 blobFileList 图片二进制数据 append 到 formdata中,用任意你喜欢的ajax库进行上传。当然也可以用原生ajax上传。

//将压缩后的二进制图片数据流append到formdata对象中上传到后台服务器
//注意:上传的是formdata对象,后台接口接收的时候,也要从formdata对象中读取二进制数据流
function formUpData(blobFiles){
    var formData = new FormData();

    formData.append("files", blobFiles);

    var xhr = new XMLHttpRequest();

    //链接你自己上传图片接口即可,这里的接口地址,是我写的示例,不可真实使用,讲解意义更大
    xhr.open('post', 'http://xxx/welcome/index/');

    xhr.onreadystatechange = function () {
 if (xhr.readyState == 4 && xhr.status == 200) {
  console.log('上传成功!');
 }
    };

    xhr.send(formData);
}
结语

完整代码以及demo:git仓库地址

文中用到了一些H5的知识点,不懂的童鞋可以进入下面的知识点资料地址,详细阅读

HTMLCanvasElement.toDataURL()

CanvasRenderingContext2D.drawImage()

FileReader()

FormData

Blob()

EXIF.js中文博客文章

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/247039.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号