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 相关文章推荐
javascript setTimeout和setInterval 的区别
Dec 08 Javascript
easyui tree带checkbox实现单选的简单实例
Nov 07 Javascript
Bootstrap table 定制提示语的加载过程
Feb 20 Javascript
JS中实现函数return多个返回值的实例
Feb 21 Javascript
jQuery插件zTree实现的多选树效果示例
Mar 08 Javascript
Angular 4依赖注入学习教程之ValueProvider的使用(七)
Jun 04 Javascript
javaScript实现滚动条事件详解
Mar 24 Javascript
vue2.0设置proxyTable使用axios进行跨域请求的方法
Oct 19 Javascript
vue--点击当前增加class,其他删除class的方法
Sep 15 Javascript
js实现导航跟随效果
Nov 17 Javascript
JavaScript学习教程之cookie与webstorage
Jun 23 Javascript
JS面向对象之单选框实现
Jan 17 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
php fputcsv命令 写csv文件遇到的小问题(多维数组连接符)
2011/05/24 PHP
WordPress的主题编写中获取头部模板和底部模板
2015/12/28 PHP
js用图作提交按钮或超连接
2008/03/26 Javascript
JQuery 入门实例1
2009/06/25 Javascript
锋利的jQuery 要点归纳(一) jQuery选择器
2010/03/21 Javascript
javascript天然的迭代器
2010/10/29 Javascript
JavaScript中将一个值转换为字符串的方法分析[译]
2012/09/21 Javascript
JavaScript+CSS控制打印格式示例介绍
2014/01/07 Javascript
jquery 中的each()跳出循环的语句
2014/05/23 Javascript
Js 正则表达式知识汇总
2014/12/02 Javascript
jquery 中ajax执行的优先级
2015/06/22 Javascript
JS实现CheckBox复选框全选、不选或全不选功能
2020/07/28 Javascript
AngularJs concepts详解及示例代码
2016/09/01 Javascript
使用webpack打包koa2 框架app
2018/02/02 Javascript
Vue项目引发的「过滤器」使用教程
2019/03/12 Javascript
利用Bootstrap Multiselect实现下拉框多选功能
2019/04/08 Javascript
Vue实现微信支付功能遇到的坑
2019/06/05 Javascript
jquery更改元素属性attr()方法操作示例
2020/05/22 jQuery
布同 统计英文单词的个数的python代码
2011/03/13 Python
Python实现多线程HTTP下载器示例
2017/02/11 Python
spyder常用快捷键(分享)
2017/07/19 Python
python的pandas工具包,保存.csv文件时不要表头的实例
2018/06/14 Python
对json字符串与python字符串的不同之处详解
2018/12/19 Python
python3.5 cv2 获取视频特定帧生成jpg图片
2019/08/28 Python
Python实现中值滤波去噪方式
2019/12/18 Python
利用django model save方法对未更改的字段依然进行了保存
2020/03/28 Python
基于python实现操作redis及消息队列
2020/08/27 Python
什么是View State?
2013/01/27 面试题
银行毕业实习自我鉴定
2013/09/19 职场文书
《槐乡五月》教学反思
2014/04/25 职场文书
2015年端午节国旗下演讲稿
2015/03/19 职场文书
在CSS中映射鼠标位置并实现通过鼠标移动控制页面元素效果(实例代码)
2021/04/22 HTML / CSS
Redis Cluster 字段模糊匹配及删除
2021/05/27 Redis
Oracle 死锁的检测查询及处理
2021/09/25 Oracle
Nginx限流和黑名单配置
2022/05/20 Servers
Fluentd搭建日志收集服务
2022/09/23 Servers