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


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 相关文章推荐
js传值 判断
Oct 26 Javascript
js获取当前月的第一天和最后一天的小例子
Nov 18 Javascript
在Iframe中获取父窗口中表单的值(示例代码)
Nov 22 Javascript
js代码实现的加入收藏效果并兼容主流浏览器
Jun 23 Javascript
微信分享的标题、缩略图、连接及描述设置方法
Oct 14 Javascript
了不起的node.js读书笔记之node.js中的特性
Dec 22 Javascript
Vue.js 父子组件通讯开发实例
Sep 06 Javascript
angular过滤器实现排序功能
Jun 27 Javascript
JS实现的全排列组合算法示例
Oct 09 Javascript
Vuex中mutations与actions的区别详解
Mar 01 Javascript
layui table 表格上添加日期控件的两种方法
Sep 28 Javascript
Element MessageBox弹框的具体使用
Jul 27 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
ob_start(),ob_start('ob_gzhandler')使用
2006/12/25 PHP
PHP中使用SimpleXML检查XML文件结构实例
2015/01/07 PHP
PHP多线程之内部多线程实例分析
2015/03/09 PHP
php判断两个浮点数是否相等的方法
2015/03/14 PHP
PHP模板引擎Smarty内建函数foreach,foreachelse用法分析
2016/04/11 PHP
PHP实现导出带样式的Excel
2016/08/28 PHP
基于CI(CodeIgniter)框架实现购物车功能的方法
2018/04/09 PHP
PHP实现普通hash分布式算法简单示例
2018/08/06 PHP
Javascript的IE和Firefox兼容性汇编
2006/07/01 Javascript
jQuery中文入门指南,翻译加实例,jQuery的起点教程
2007/02/09 Javascript
JS类库Bindows1.3中的内存释放方式分析
2007/03/08 Javascript
javascript下判断一个元素是否存在的代码
2010/03/05 Javascript
js动态往表格的td中添加图片并注册事件
2014/06/12 Javascript
jQuery对象的length属性用法实例
2014/12/27 Javascript
jQuery菜单插件用法实例
2015/07/25 Javascript
详解maxlength属性在textarea里奇怪的表现
2015/12/27 Javascript
JavaScript中的闭包
2016/02/24 Javascript
一句jQuery代码实现返回顶部效果(简单实用)
2016/12/28 Javascript
jQuery Validate 无法验证 chosen-select元素的解决方法
2017/05/17 jQuery
Node.js log4js日志管理详解
2018/07/31 Javascript
vue-router源码之history类的浅析
2019/05/21 Javascript
微信小程序学习总结(四)事件与冒泡实例分析
2020/06/04 Javascript
python标准日志模块logging的使用方法
2013/11/01 Python
Python中获取对象信息的方法
2015/04/27 Python
python matplotlib坐标轴设置的方法
2017/12/05 Python
Python中pandas模块DataFrame创建方法示例
2018/06/20 Python
Python3利用print输出带颜色的彩色字体示例代码
2019/04/08 Python
Win10+GPU版Pytorch1.1安装的安装步骤
2019/09/27 Python
css3 仿写阿里云水纹效果的示例代码
2018/02/10 HTML / CSS
物业管理公司实习生自我鉴定
2013/09/19 职场文书
机修工工作职责
2014/02/21 职场文书
幼儿园毕业教师感言
2014/02/21 职场文书
幼儿教师小班个人总结
2015/02/05 职场文书
2015高三毕业寄语赠言
2015/02/27 职场文书
Js类的构建与继承案例详解
2021/09/15 Javascript
sentinel支持的redis高可用集群配置详解
2022/04/01 Redis