移动前端图片压缩上传的实例


Posted in Javascript onDecember 06, 2017

摘要:之前在做一个小游戏平台项目,有个“用户中心”模块,就涉及到了头像上传的功能。在做移动端图片上传的时候,传的都是手机本地图片,而本地图片一般都相对比较大,拿现在的智能手机来说,平时拍很多图片都是两三兆的,如果直接这样上传,那图片就太大了,如果用户用的是移动流量,完全把图片上传显然不是一个好办法。所以上传之前进行压缩处理是必要的,在网上找了很多资料之后,尝试了很多方法,遇到了很多坑,比如安卓能够成功压缩上传图片,在ios上却上传不了,折腾了很久才发现ios的坑。一下这种已经进过实践证明是可行的,几兆的图片最后都能压缩到我们后端要求的200k以内!这么可行的方法必须给大家看看【ps:都是些东拼西凑别人的方法出来的,嘻嘻~】。

目前来说,HTML5的各种新API都在移动端的webkit上得到了较好的实现。根据查看caniuse,本demo里使用到的FileReader、Blob、Formdata对象均已在大部分移动设备浏览器中得到了实现(safari6.0+、android 3.0+),所以直接在前端压缩图片,已经成了很多移动端图片上传的必备功能了。

在移动端压缩图片并且上传主要用到filereader、canvas 以及 formdata 这三个h5的api。逻辑并不难。整个过程就是:

(1)用户使用input file上传图片的时候,用filereader读取用户上传的图片数据(base64格式)

(2)把图片数据传入img对象,然后将img绘制到canvas上,再调用canvas.toDataURL对图片进行压缩

(3)获取到压缩后的base64格式图片数据,转成二进制塞入formdata,再通过XmlHttpRequest提交formdata。

如此三步,就完成了图片的压缩和上传。

说起来好像挺简单,其实还是有些坑的。接下来就直接用代码进行分析:

【一】获取图片数据

先是获取图片数据,也就是监听input file的change事件,然后获取到上传的文件对象files,将类数组的files转成数组,然后进行forEach遍历。

接着判断文件类型,如果不是图片则不作处理。如果是图片就实例化一个filereader,以base64格式读取上传的文件数据,判断数据长度,如果大于200KB的图片就调用compress方法进行压缩,否则调用upload方法进行上传。

filechooser.onchange = function() {
 if (!this.files.length) return;
 var files = Array.prototype.slice.call(this.files);
 if (files.length > 9) {
  alert("最多同时只可上传9张图片");
  return;
 }
 files.forEach(function(file, i) {
  if (!/\/(?:jpeg|png|gif)/i.test(file.type)) return;
  var reader = new FileReader();
  var li = document.createElement("li");
  li.innerHTML = '<div class="progress"><span></span></div>';
  $(".img-list").append($(li));
  reader.onload = function() {
   var result = this.result;
   var img = new Image();
   img.src = result;
   //如果图片大小小于200kb,则直接上传
   if (result.length <= maxsize) {
    $(li).css("background-image", "url(" + result + ")");
    img = null;
    upload(result, file.type, $(li));
    return;
   }
   //图片加载完毕之后进行压缩,然后上传
   if (img.complete) {
    callback();
   } else {
    img.onload = callback;
   }
   function callback() {
    var data = compress(img);
    $(li).css("background-image", "url(" + data + ")");
    upload(data, file.type, $(li));
    img = null;
   }
  };
  reader.readAsDataURL(file);
 })
};

【2】压缩图片

上面做完图片数据的获取后,就可以做compress压缩图片的方法了。而压缩图片也并不是直接把图片绘制到canvas再调用一下toDataURL就行的。

在IOS中,canvas绘制图片是有两个限制的:

首先是图片的大小,如果图片的大小超过两百万像素,图片也是无法绘制到canvas上的,调用drawImage的时候不会报错,但是你用toDataURL获取图片数据的时候获取到的是空的图片数据。

再者就是canvas的大小有限制,如果canvas的大小大于大概五百万像素(即宽高乘积)的时候,不仅图片画不出来,其他什么东西也都是画不出来的。

应对第一种限制,处理办法就是瓦片绘制了。瓦片绘制,也就是将图片分割成多块绘制到canvas上,我代码里的做法是把图片分割成100万像素一块的大小,再绘制到canvas上。

而应对第二种限制,我的处理办法是对图片的宽高进行适当压缩,我代码里为了保险起见,设的上限是四百万像素,如果图片大于四百万像素就压缩到小于四百万像素。四百万像素的图片应该够了,算起来宽高都有2000X2000了。

如此一来就解决了IOS上的两种限制了。

除了上面所述的限制,还有两个坑,一个就是canvas的toDataURL是只能压缩jpg的,当用户上传的图片是png的话,就需要转成jpg,也就是统一用canvas.toDataURL('image/jpeg', 0.1) , 类型统一设成jpeg,而压缩比就自己控制了。

另一个就是如果是png转jpg,绘制到canvas上的时候,canvas存在透明区域的话,当转成jpg的时候透明区域会变成黑色,因为canvas的透明像素默认为rgba(0,0,0,0),所以转成jpg就变成rgba(0,0,0,1)了,也就是透明背景会变成了黑色。解决办法就是绘制之前在canvas上铺一层白色的底色。

function compress(img) {
 var initSize = img.src.length;
 var width = img.width;
 var height = img.height;
 //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
 var ratio;
 if ((ratio = width * height / 4000000) > 1) {
  ratio = Math.sqrt(ratio);
  width /= ratio;
  height /= ratio;
 } else {
  ratio = 1;
 }
 canvas.width = width;
 canvas.height = height;
 //铺底色
 ctx.fillStyle = "#fff";
 ctx.fillRect(0, 0, canvas.width, canvas.height);
 //如果图片像素大于100万则使用瓦片绘制
 var count;
 if ((count = width * height / 1000000) > 1) {
  count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片
  //计算每块瓦片的宽和高
  var nw = ~~(width / count);
  var nh = ~~(height / count);
  tCanvas.width = nw;
  tCanvas.height = nh;
  for (var i = 0; i < count; i++) {
   for (var j = 0; j < count; j++) {
    tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
    ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
   }
  }
 } else {
  ctx.drawImage(img, 0, 0, width, height);
 }
 //进行最小压缩
 var ndata = canvas.toDataURL('image/jpeg', 0.1);
 console.log('压缩前:' + initSize);
 console.log('压缩后:' + ndata.length);
 console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%");
 tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
 return ndata;
}

【三】图片上传

完成图片压缩后,就可以塞进formdata里进行上传了,先将base64数据转成字符串,再实例化一个ArrayBuffer,然后将字符串以8位整型的格式传入ArrayBuffer,再通过BlobBuilder或者Blob对象,将8位整型的ArrayBuffer转成二进制对象blob,然后把blob对象append到formdata里,再通过ajax发送给后台即可。

XmlHttpRequest2中不仅可以发送大数据,还多出了比如获取发送进度的API,我代码里也进行了简单的实现。

//图片上传,将base64的图片转成二进制对象,塞进formdata上传
function upload(basestr, type, $li) {
 var text = window.atob(basestr.split(",")[1]);
 var buffer = new ArrayBuffer(text.length);
 var ubuffer = new Uint8Array(buffer);
 var pecent = 0,
  loop = null;
 for (var i = 0; i < text.length; i++) {
  ubuffer[i] = text.charCodeAt(i);
 }
 var Builder = window.WebKitBlobBuilder || window.MozBlobBuilder;
 var blob;
 if (Builder) {
  var builder = new Builder();
  builder.append(buffer);
  blob = builder.getBlob(type);
 } else {
  blob = new window.Blob([buffer], {
   type: type
  });
 }
 var xhr = new XMLHttpRequest();
 var formdata = new FormData();
 formdata.append('imagefile', blob);
 xhr.open('post', '/cupload');
 xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
   console.log('上传成功:' + xhr.responseText);
   clearInterval(loop);
   //当收到该消息时上传完毕
   $li.find(".progress span").animate({
    'width': "100%"
   }, pecent < 95 ? 200 : 0, function() {
    $(this).html("上传成功");
   });
   $(".pic-list").append('<a href="' + xhr.responseText + '">' + xhr.responseText + '<img src="' + xhr.responseText + '" /></a>')
  }
 };
 //数据发送进度,前50%展示该进度
 xhr.upload.addEventListener('progress', function(e) {
  if (loop) return;
  pecent = ~~(100 * e.loaded / e.total) / 2;
  $li.find(".progress span").css('width', pecent + "%");
  if (pecent == 50) {
   mockProgress();
  }
 }, false);
 //数据后50%用模拟进度
 function mockProgress() {
  if (loop) return;
  loop = setInterval(function() {
   pecent++;
   $li.find(".progress span").css('width', pecent + "%");
   if (pecent == 99) {
    clearInterval(loop);
   }
  }, 100)
 }
 xhr.send(formdata);
}

至此,整个上传的前端图片压缩就完成了,因为是用了formdata提交,所以后台接数据的时候就跟普通form表单提交数据一样处理即可。

以上这篇移动前端图片压缩上传的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
打造基于jQuery的高性能TreeView(asp.net)
Feb 23 Javascript
JS上传图片前实现图片预览效果的方法
Mar 02 Javascript
jQuery Mobile动态刷新页面样式的实现方法
May 28 Javascript
JS禁止查看网页源代码的实现方法
Oct 12 Javascript
Node.JS利用PhantomJs抓取网页入门教程
May 19 Javascript
jquery.rotate.js实现可选抽奖次数和中奖内容的转盘抽奖代码
Aug 23 jQuery
关于Vue的路由权限管理的示例代码
Mar 06 Javascript
新版vue-cli模板下本地开发环境使用node服务器跨域的方法
Apr 03 Javascript
解决vue+element 键盘回车事件导致页面刷新的问题
Aug 25 Javascript
了解Javascript中函数作为对象的魅力
Jun 19 Javascript
Element Notification通知的实现示例
Jul 27 Javascript
Ajax常用封装库——Axios的使用
May 08 Javascript
详细分析JS函数去抖和节流
Dec 05 #Javascript
微信小程序实现点击按钮修改字体颜色功能【附demo源码下载】
Dec 05 #Javascript
微信小程序实现动态改变view标签宽度和高度的方法【附demo源码下载】
Dec 05 #Javascript
vue+iview写个弹框的示例代码
Dec 05 #Javascript
Node.js创建Web、TCP服务器
Dec 05 #Javascript
实例讲解javascript实现异步图片上传方法
Dec 05 #Javascript
jquery如何实现点击空白处隐藏元素
Dec 05 #jQuery
You might like
解决phpmyadmin 乱码,支持gb2312和utf-8
2006/11/20 PHP
php文档更新介绍
2011/07/22 PHP
php截取后台登陆密码的代码
2012/05/05 PHP
PHP命令行脚本接收传入参数的三种方式
2014/08/20 PHP
php实现两表合并成新表并且有序排列的方法
2014/12/05 PHP
使用PHP免费发送定时短信的实例
2016/10/24 PHP
php array_key_exists() 与 isset() 的区别
2016/10/24 PHP
PHP正则删除HTML代码中宽高样式的方法
2017/06/12 PHP
Laravel的Auth验证Token验证使用自定义Redis的例子
2019/09/30 PHP
php 函数中静态变量使用的问题实例分析
2020/03/05 PHP
JavaScript 构造函数 面相对象学习必备知识
2010/06/09 Javascript
jqGrid jQuery 表格插件测试代码
2011/08/23 Javascript
深入理解JavaScript系列(13) This? Yes,this!
2012/01/18 Javascript
JS中的prototype与面向对象的实例讲解
2013/05/22 Javascript
原生JavaScript实现瀑布流布局
2020/06/28 Javascript
JS Array.slice 截取数组的实现方法
2016/01/02 Javascript
Jquery attr()方法 属性赋值和属性获取详解
2016/04/15 Javascript
省市选择的简单实现(基于zepto.js)
2016/06/21 Javascript
使用prop解决一个checkbox选中后再次选中失效的问题
2017/07/05 Javascript
使用VUE+iView+.Net Core上传图片的方法示例
2019/01/04 Javascript
layui-table获得当前行的上/下一行数据的例子
2019/09/24 Javascript
在vue中使用jsx语法的使用方法
2019/09/30 Javascript
基于PHP pthreads实现多线程代码实例
2020/06/24 Javascript
python使用urllib2实现发送带cookie的请求
2015/04/28 Python
Django web框架使用url path name详解
2019/04/29 Python
计算机网络毕业生自荐信
2013/10/01 职场文书
电子商务专业个人的自我评价
2013/11/19 职场文书
遗体告别仪式答谢词
2014/01/23 职场文书
身边的榜样活动方案
2014/08/20 职场文书
我的梦想演讲稿500字
2014/08/21 职场文书
幼儿园教师节演讲稿
2014/09/03 职场文书
对公司的意见和建议
2015/06/04 职场文书
2016寒假社会实践心得体会范文
2015/10/09 职场文书
2015年乡镇食品安全工作总结
2015/10/22 职场文书
2019学生会干事辞职信
2019/06/27 职场文书
Java Kafka 消费积压监控的示例代码
2021/07/01 Java/Android