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国旗变换效果代码
Aug 13 Javascript
Span元素的width属性无效果原因及解决方案
Jan 15 Javascript
23个Javascript弹出窗口特效整理
Feb 25 Javascript
javascript动态创建及删除元素的方法
Dec 22 Javascript
解决bootstrap中modal遇到Esc键无法关闭页面
Mar 09 Javascript
jquery实现鼠标悬浮停止轮播特效
Aug 20 Javascript
js实现的下拉框二级联动效果
Apr 30 Javascript
基于JS实现9种不同的面包屑和分布式多步骤导航效果
Feb 21 Javascript
js实现图片轮播效果学习笔记
Jul 26 Javascript
Vue完整项目构建(进阶篇)
Feb 10 Javascript
vuex提交state&amp;&amp;实时监听state数据的改变方法
Sep 16 Javascript
Vue项目部署的实现(阿里云+Nginx代理+PM2)
Mar 26 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警告Cannot use a scalar value as an array的解决方法
2012/01/11 PHP
ubuntu10.04配置 nginx+php-fpm模式的详解
2013/06/03 PHP
php日历制作代码分享
2014/01/20 PHP
PHP数据库操作之基于Mysqli的数据库操作类库
2014/04/19 PHP
thinkPHP自动验证、自动添加及表单错误问题分析
2016/10/17 PHP
div层的移动及性能优化
2010/11/16 Javascript
firefox下input type=&quot;file&quot;的size是多大
2011/10/24 Javascript
jquery实现简单的无缝滚动
2015/04/15 Javascript
bootstrap table 多选框分页保留示例代码
2017/03/08 Javascript
微信小程序微信支付接入开发实例详解
2017/04/12 Javascript
详解vue-resource promise兼容性问题
2017/06/20 Javascript
nodejs项目windows下开机自启动的方法
2017/11/22 NodeJs
vue中导出Excel表格的实现代码
2018/10/18 Javascript
小程序自定义模板实现吸顶功能
2020/01/08 Javascript
简单了解JavaScript弹窗实现代码
2020/05/07 Javascript
解决removeEventListener 无法清除监听的问题
2020/10/30 Javascript
vue 项目@change多个参数传值多个事件的操作
2021/01/29 Vue.js
Eclipse和PyDev搭建完美Python开发环境教程(Windows篇)
2016/11/16 Python
python字符串中的单双引
2017/02/16 Python
python自动化生成IOS的图标
2018/11/13 Python
Python HTML解析器BeautifulSoup用法实例详解【爬虫解析器】
2019/04/05 Python
修改 CentOS 6.x 上默认Python的方法
2019/09/06 Python
Python解释器及PyCharm工具安装过程
2020/02/26 Python
jupyter notebook清除输出方式
2020/04/10 Python
python中线程和进程有何区别
2020/06/17 Python
在keras里实现自定义上采样层
2020/06/28 Python
基于Jquery和Css3代码制作可以缩放的搜索框
2015/11/19 HTML / CSS
CSS3实现头像旋转效果
2017/03/13 HTML / CSS
自荐信的五个重要部分
2013/10/29 职场文书
市场营销专业毕业生自荐信
2013/11/02 职场文书
党员自我评价2015
2015/03/03 职场文书
工程服务质量承诺书
2015/04/29 职场文书
搞笑婚庆主持词
2015/06/29 职场文书
Go语言并发编程 sync.Once
2021/10/16 Golang
delete in子查询不走索引问题分析
2022/07/07 MySQL
Python可视化神器pyecharts之绘制地理图表练习
2022/07/07 Python