vue实现压缩图片预览并上传功能(promise封装)


Posted in Javascript onJanuary 10, 2019

本文实例为大家分享了vue实现压缩图片预览并上传的具体代码,供大家参考,具体内容如下

主要用到filereader、canvas 以及 formdata 这三个h5的api

过程大致分为三步:

用户使用input file上传图片的时候,用filereader读取用户上传的图片数据(base64格式)
把图片数据传入img对象,然后将img绘制到canvas上,再调用canvas.toDataURL对图片进行压缩
获取到压缩后的base64格式图片数据,转成二进制塞入formdata,再通过XmlHttpRequest提交formdata。

模板:

<template>
 <div class="image-box">
 <input type="file" accept="image/*" @change="imageHandle">
 <img ref="upImg"/>
 </div>
</template>

获取图片数据

methods: {
  //监听input file的change事件
 imageHandle(e) {
  //**这个是必不可少的,在下面的reader.onload中this就不再指vm了**
  let that = this;
  let maxSize = 100 * 1024;
  let files = e.srcElement.files;
  if (!files.length) return; //文件长度大于0
  if (!/^image\//.test(files[0].type)) return; //必须是图片才处理
  if (!window.FileReader) return; //支持FileReader
  //创建filereader对象
  let reader = new FileReader();
  reader.readAsDataURL(files[0]); //将图片转成base64格式
  reader.onload = function() {
  let result = this.result;
  let img = new Image();
  img.src = result;
  let formdata = new FormData();
  if (this.result.length <= maxSize) {
   that.$refs.upImg.src = result; //预览图片
   img = null;
   //上传图片
   formdata.append("image", that._upload(result, files[0].name, files[0].type));
   that.$store.dispatch("uploadImage", formdata)
    .then(data => {
     if (data === 1) {
     that.$toast("上传成功", "success");
     } else if (data === -1) {
     that.$toast("图片为空", "error");
     } else {
     that.$toast("上传失败", "error");
     }
    })
    .catch(error => that.$toast("上传失败", "error"));
  } else {
   img.onload = function() {
   //压缩图片
   let data = that._compress(img);
   //图片预览
   that.$refs.upImg.src = data;
   //上传图片
   formdata.append("image", that._upload(data, files[0].name, files[0].type));
   that.$store.dispatch("uploadImage", formdata)
     .then(data => {
      if (data === 1) {
      that.$toast("上传成功", "success");
      } else if (data === -1) {
      that.$toast("图片为空", "error");
      } else {
      that.$toast("上传失败", "error");
      }
     })
     .catch(error => that.$toast("上传失败", "error"));
   };
  }
  };
 },

压缩图片

在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上铺一层白色的底色。

_compress(img) {
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  //瓦片
  let tCanvas = document.createElement("canvas");
  let tctx = tCanvas.getContext("2d");
  let initSize = img.src.length;
  let width = img.width;
  let height = img.height;
  //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
  let ratio;
  if ((ratio = (width * height) / 4000000) > 1) {
  ratio = Math.sqrt(ratio);
  widht /= ratio;
  height /= ratio;
  } else {
  ratio = 1;
  }
  canvas.width = width;
  canvas.height = height;
  //铺底色
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  //如果图片像素大于100万则使用瓦片绘制
  let count;
  if ((count = (width * height) / 1000000) > 1) {
  count = ~~(Math.sqrt(count) + 1); //计算要分成多少瓦片,~~在这里表示取整
  //计算每块瓦片的宽高
  let nw = ~~(width / count);
  let nh = ~~(height / count);
  tCanvas.width = nw;
  tCanvas.height = nh;
  for (let i = 0; i < count; i++) {
   for (let 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, canvas.width, canvas.height);
  }
  //进行压缩
  let ndata = canvas.toDataURL("image/jpeg", 0.3);
  tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
  return ndata;
 },

上传

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

_upload(data, name, type) {
  let text = window.atob(data.split(",")[1]);
  let buffer = new ArrayBuffer(text.length);
  let ubuffer = new Uint8Array(buffer);
  let pecent = 0,
  loop = null;

  for (var i = 0; i < text.length; i++) {
  ubuffer[i] = text.charCodeAt(i);
  }

  let Builder =
  window.BlobBuilder ||
  window.WebKitBlobBuilder ||
  window.MozBlobBuilder ||
  window.MSBlobBuilder;
  let blob;
  if (Builder) {
  var builder = new Builder();
  builder.append(buffer);
  blob = builder.getBlob(type);
  } else {
  blob = new window.Blob([ubuffer], { type: type });
  }
  // blob 转file
  var fileOfBlob = new File([blob], name, { type: type });
  return fileOfBlob;
 }
 }

将图片压缩上传封装到一个js文件里

const UploadImg = {
 imageHandle(files, maxSize, imgDom) {
  let that = this;
  let formdata = new FormData();
  let reader = new FileReader();
  reader.readAsDataURL(files[0]); //将图片转成base64格式
  //reader.onload是异步,要用到Promise对象将值返回出去
  return new Promise((resolved, rejected) => {
   reader.onload = function () {
    let result = this.result;
    let img = new Image();
    img.src = result;
    if (this.result.length <= maxSize) {
     imgDom.src = result;
     img = null;
     formdata.append("image", that._upload(result, files[0].name, files[0].type));
     resolved(formdata);
    } else {
     img.onload = function () {
      let data = that._compress(img);
      imgDom.src = data;
      formdata.append("image", that._upload(data, files[0].name, files[0].type));
      resolved(formdata);
     };
    }
   };
  })

 },
 _compress(img) {
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  //瓦片
  let tCanvas = document.createElement("canvas");
  let tctx = tCanvas.getContext("2d");
  let width = img.width;
  let height = img.height;
  //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
  let ratio;
  if ((ratio = (width * height) / 4000000) > 1) {
   ratio = Math.sqrt(ratio);
   widht /= ratio;
   height /= ratio;
  } else {
   ratio = 1;
  }
  canvas.width = width;
  canvas.height = height;
  //铺底色
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  //如果图片像素大于100万则使用瓦片绘制
  let count;
  if ((count = (width * height) / 1000000) > 1) {
   count = ~~(Math.sqrt(count) + 1); //计算要分成多少瓦片
   //计算每块瓦片的宽高
   let nw = ~~(width / count);
   let nh = ~~(height / count);
   tCanvas.width = nw;
   tCanvas.height = nh;
   for (let i = 0; i < count; i++) {
    for (let 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, canvas.width, canvas.height);
  }
  //进行最小压缩
  let ndata = canvas.toDataURL("image/jpeg", 0.3);
  tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
  return ndata;
 },
 _upload(data, name, type) {
  let text = window.atob(data.split(",")[1]);
  let buffer = new ArrayBuffer(text.length);
  let ubuffer = new Uint8Array(buffer);

  for (var i = 0; i < text.length; i++) {
   ubuffer[i] = text.charCodeAt(i);
  }

  let Builder =
   window.BlobBuilder ||
   window.WebKitBlobBuilder ||
   window.MozBlobBuilder ||
   window.MSBlobBuilder;
  let blob;
  if (Builder) {
   var builder = new Builder();
   builder.append(buffer);
   blob = builder.getBlob(type);
  } else {
   blob = new window.Blob([ubuffer], { type: type });
  }
  // blob 转file
  var fileOfBlob = new File([blob], name, { type: type });
  return fileOfBlob;
 }
}

export default UploadImg

调用代码

import UploadImg from "../../util/uploadImg";

methods: {
 imageHandle(e) {
  let maxSize = 100 * 1024;
  let imgDom = this.$refs.upImg;
  let files = e.srcElement.files;
  if (!files.length) return; //文件长度大于0
  if (!/^image\//.test(files[0].type)) return; //必须是图片才处理
  if (!window.FileReader) return; //支持FileReader

  if (this.docEntry === "" || this.lineId === "") {
  this.$toast("请填写完整信息", "error");
  return;
  }
  // let formdata = new FormData();
  UploadImg.imageHandle(files, maxSize, imgDom).then(formdata => {
  formdata.append("docEntry", this.docEntry);
  formdata.append("lineId", this.lineId);
  formdata.append("action", "ProductionListImage");
  this.$store
   .dispatch("uploadImage", formdata)
   .then(data => {
   if (data === 1) {
    this.$toast("上传成功", "success");
   } else if (data === -1) {
    this.$toast("图片为空", "error");
   } else {
    this.$toast("上传失败", "error");
   }
   })
   .catch(error => this.$toast("上传失败", "error"));
  });
 }
 }

参考链接:移动端利用H5实现压缩图片上传功能

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
深入分析jQuery的ready函数是如何工作的(工作原理)
Dec 17 Javascript
使用postMesssage()实现iframe跨域页面间的信息传递
Mar 29 Javascript
javascript创建cookie、读取cookie
Mar 31 Javascript
Seajs 简易文档 提供简单、极致的模块化开发体验
Apr 13 Javascript
Bootstrap弹出框modal上层的输入框不能获得焦点问题的解决方法
Dec 13 Javascript
微信小程序 连续旋转动画(this.animation.rotate)详解
Apr 07 Javascript
angularjs下拉框空白的解决办法
Jun 20 Javascript
webstrom Debug 调试vue项目的方法步骤
Jul 17 Javascript
浅谈angular2子组件的事件传递(任意组件事件传递)
Sep 30 Javascript
JavaScript中的 new 命令
May 22 Javascript
微信小程序tab切换可滑动切换导航栏跟随滚动实现代码
Sep 04 Javascript
关于JS中的作用域中的问题思考分享
Apr 06 Javascript
微信小程序地图(map)组件点击(tap)获取经纬度的方法
Jan 10 #Javascript
最简单的JS实现json转csv的方法
Jan 10 #Javascript
puppeteer实现html截图的示例代码
Jan 10 #Javascript
其实你可以少写点if else与switch(推荐)
Jan 10 #Javascript
微信小程序提取公用函数到util.js及使用方法示例
Jan 10 #Javascript
浅谈JavaScript 代码简洁之道
Jan 09 #Javascript
react组件从搭建脚手架到在npm发布的步骤实现
Jan 09 #Javascript
You might like
基于empty函数的输出详解
2013/06/17 PHP
解析PHP对现有搜索引擎的调用
2013/06/25 PHP
laravel安装zend opcache加速器教程
2015/03/02 PHP
深入讲解PHP的Yii框架中的属性(Property)
2016/03/18 PHP
php中curl和soap方式请求服务超时问题的解决
2018/06/11 PHP
PHP进阶学习之命名空间基本用法分析
2019/06/18 PHP
PHP实现笛卡尔积算法的实例讲解
2019/12/22 PHP
关于IFRAME 自适应高度的研究
2006/07/20 Javascript
javascript 中对象的继承〔转贴〕
2007/01/22 Javascript
jQuery实现Twitter的自动文字补齐特效
2014/11/28 Javascript
学习Bootstrap滚动监听 附调用方法
2016/07/02 Javascript
Angular工具方法学习
2016/12/26 Javascript
记一次vue去除#问题处理经过小结
2019/01/24 Javascript
扫微信小程序码实现网站登陆实现解析
2019/08/20 Javascript
ES5新增数组的实现方法
2020/05/12 Javascript
Node.js API详解之 vm模块用法实例分析
2020/05/27 Javascript
vue修改Element的el-table样式的4种方法
2020/09/17 Javascript
微信小程序入门之绘制时钟
2020/10/22 Javascript
python实现ipsec开权限实例
2014/11/11 Python
在Python中用split()方法分割字符串的使用介绍
2015/05/20 Python
Python SQLite3数据库日期与时间常见函数用法分析
2017/08/14 Python
Python 字符串转换为整形和浮点类型的方法
2018/07/17 Python
Python3爬虫爬取英雄联盟高清桌面壁纸功能示例【基于Scrapy框架】
2018/12/05 Python
django框架使用方法详解
2019/07/18 Python
推荐10个CSS3 制作的创意下拉菜单效果
2014/02/11 HTML / CSS
CSS3 函数技巧 用css 实现js实现的事情(clac Counters Tooltip)
2017/08/15 HTML / CSS
HTML5页面无缝闪开的问题及解决方案
2020/06/11 HTML / CSS
美国花园雕像和家居装饰网上商店:Design Toscano
2019/03/09 全球购物
数据库专业英语
2012/11/30 面试题
暑期研修感言
2014/02/17 职场文书
机械专业应届毕业生自荐书
2014/06/12 职场文书
学生会任命书范本
2015/09/21 职场文书
2019年怎样才能撰写出优秀的自荐信
2019/03/25 职场文书
Nginx域名转发使用场景代码实例
2021/03/31 Servers
Java Spring Lifecycle的使用
2022/05/06 Java/Android
鸿蒙3.0体验感怎么样? 鸿蒙3.0系统评测向
2022/08/14 数码科技