基于vue + axios + lrz.js 微信端图片压缩上传
业务场景
微信端项目是基于Vux + Axios构建的,关于图片上传的业务场景有以下几点需求:
1、单张图片上传(如个人头像,实名认证等业务)
2、多张图片上传(如某类工单记录)
3、上传图片时期望能按指定尺寸压缩处理
4、上传图片可以从相册中选择或者直接拍照
遇到的坑
采用微信JSSDK上传图片
在之前开发的项目中(mui + jquery),有使用过微信JSSDK的接口上传图片,本想应该能快速迁移至此项目。事实证明编程没有简单的事:
1、按指定尺寸压缩图片
JSSDK提供的接口wx.chooseImage 是不能指定图片压缩尺寸的,只能在后端的接口通过localId获取图片时,再转换成指定的尺寸。
2、微信JSSDK的接口权限验证
只要是单页面应用项目,微信JSSDK注入权限验证都会有这个坑,而这个与路由模式(hash 或 history)也有关联。有关此坑, 后续会再次写文总结。参考解决方案[微信JSSDK] 解决SDK注入权限验证 安卓正常,IOS出现config fail
经过权衡考虑网页可能需要在微信以外的浏览器上也能上传文件,顾后来放弃了采用微信JSSDK接口上传图片的方式。
android版微信,input onchange事件不触发
这个坑,圈内有很多人踩过了。在PC端测试是正常的,发布之后,微信端上传时能选择文件,但之后没有任何效果。日志跟踪,后台的api都未调用,由此判断是input的onchange事件未被触发。
解决方案, 更改input的 accept属性:
<input ref="file" type="file" accept="image/jpeg,image/png" @change="selectImgs" />
将以上代码更改为:
<input ref="file" type="file" accept="image/*" @change="selectImgs" />
如果不允许从相册中选择,只能拍照,增加capture="camera":
<input ref="file" type="file" accept="image/*" capture="camera" @change="selectImgs" />
(注:如果场景支持从相册选择或拍照,测试发现某些机型拍照后返回到了主页。哈哈,也有可能是其他因素引起的问题,未做深究了)
使用Lrz.js压缩图片
目前手机拍照的图片文件大小一般在3-4M,如果在上传时不做压缩处理会相当浪费流量并且占用服务器的存储空间(期望上传原图的另做讨论)。如果能够在前端压缩处理,那肯定是最理想的方案。而lrz.js则提供了前端图片文件的压缩方案,并且可以指定尺寸压缩。实测:3M左右的图片文件,按宽度450px尺寸压缩上传后的文件大小在500kb左右,上传时间2s以内。
其核心源码,如下:
selectImgs () {
let file = this.$refs.file.files[0]
lrz(file, { width: 450, fieldName: 'file' }).then((rst) => {
var xhr = new XMLHttpRequest()
xhr.open('POST', 'http://xxx.com/upload')
xhr.onload = () => {
if (xhr.status === 200 || xhr.status === 304) {
// 无论后端抛出何种错误,都会走这里
try {
// 如果后端跑异常,则能解析成功, 否则解析不成功
let resp = JSON.parse(xhr.responseText)
console.log('response: ', resp)
} catch (e) {
this.imageUrl = xhr.responseText
}
}
}
// 添加参数
rst.formData.append('folder', 'wxAvatar') // 保存的文件夹
rst.formData.append('base64', rst.base64)
// 触发上传
xhr.send(rst.formData)
return rst
})
}
单个图片上传组件完整代码,如下(注: icon图标使用的是svg-icon组件):
<template>
<div class="imgUploader">
<section v-if="imageUrl"
class="file-item ">
<img :src="imageUrl"
alt="">
<span class="file-remove"
@click="remove()">+</span>
</section>
<section v-else
class="file-item">
<div class="add">
<svg-icon v-if="!text"
class="icon"
icon-class="plus" />
<span v-if="text"
class="text">{{text}}</span>
<input type="file"
accept="image/*"
@change="selectImgs"
ref="file">
</div>
</section>
</div>
</template>
<script>
import lrz from 'lrz'
export default {
props: {
text: String,
// 压缩尺寸,默认宽度为450px
size: {
type: Number,
default: 450
}
},
data () {
return {
img: {
name: '',
src: ''
},
uploadUrl: 'http://ff-ff.xxx.cn/UploaderV2/Base64FileUpload',
imageUrl: ''
}
},
watch: {
imageUrl (val, oldVal) {
this.$emit('input', val)
},
value (val) {
this.imageUrl = val
}
},
mounted () {
this.imageUrl = this.value
},
methods: {
// 选择图片
selectImgs () {
let file = this.$refs.file.files[0]
lrz(file, { width: this.size, fieldName: 'file' }).then((rst) => {
var xhr = new XMLHttpRequest()
xhr.open('POST', this.uploadUrl)
xhr.onload = () => {
if (xhr.status === 200 || xhr.status === 304) {
// 无论后端抛出何种错误,都会走这里
try {
// 如果后端跑异常,则能解析成功, 否则解析不成功
let resp = JSON.parse(xhr.responseText)
console.log('response: ', resp)
} catch (e) {
this.imageUrl = xhr.responseText
}
}
}
// 添加参数
rst.formData.append('folder', this.folder) // 保存的文件夹
rst.formData.append('base64', rst.base64)
// 触发上传
xhr.send(rst.formData)
return rst
})
},
// 移除图片
remove () {
this.imageUrl = ''
}
}
}
</script>
<style lang="less" scoped>
.imgUploader {
margin-top: 0.5rem;
.file-item {
float: left;
position: relative;
width: 100px;
text-align: center;
left: 2rem;
img {
width: 100px;
height: 100px;
border: 1px solid #ececec;
}
.file-remove {
position: absolute;
right: 0px;
top: 4px;
width: 14px;
height: 14px;
color: white;
cursor: pointer;
line-height: 12px;
border-radius: 100%;
transform: rotate(45deg);
background: rgba(0, 0, 0, 0.5);
}
&:hover .file-remove {
display: inline;
}
.file-name {
margin: 0;
height: 40px;
word-break: break-all;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
.add {
width: 100px;
height: 100px;
float: left;
text-align: center;
line-height: 100px;
font-size: 30px;
cursor: pointer;
border: 1px dashed #40c2da;
color: #40c2da;
position: relative;
background: #ffffff;
.icon {
font-size: 1.4rem;
color: #7dd2d9;
vertical-align: -0.25rem;
}
.text {
font-size: 1.2rem;
color: #7dd2d9;
vertical-align: 0.25rem;
}
}
}
input[type="file"] {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1px solid #000;
opacity: 0;
}
</style>
后端图片存储处理
后端api对图片的处理,是必不可少的环节,需要将前端提交过来的base64字符串转换成图片格式,并存放至指定的文件夹,接口返回图片的Url路径。各项目后端对图片的处理逻辑都不一致,以下方案仅供参考(我们使用asp.net MVC 构建了独立的文件存储站点)。
其核心源码,如下:
/// <summary>
/// 图片文件base64上传
/// </summary>
/// <param name="folder">对应文件夹位置</param>
/// <param name="base64">图片文件base64字符串</param>
/// <returns></returns>
public ActionResult Base64FileUpload(string folder, string base64)
{
var context = System.Web.HttpContext.Current;
context.Response.ClearContent();
// 因为前端调用时,需要做跨域处理
context.Response.AddHeader("Access-Control-Allow-Origin", "*");
context.Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
context.Response.AddHeader("Access-Control-Allow-Headers", "content-type");
context.Response.AddHeader("Access-Control-Max-Age", "30");
if (context.Request.HttpMethod.Equals("OPTIONS"))
{
return Content("");
}
var resultStr = base64.Substring(base64.IndexOf(",") + 1);//需要去掉头部信息,这很重要
byte[] bytes = Convert.FromBase64String(resultStr);
var fileName = Guid.NewGuid().ToString() + ".png";
if (folder.IsEmpty()) folder = "folder";
//本地上传
string root = string.Format("/Resource/{0}/", folder);
string virtualPath = root + fileName;
string path = Server.MapPath("~" + virtualPath);
//创建文件夹
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
System.IO.MemoryStream ms = new System.IO.MemoryStream(bytes);//转换成无法调整大小的MemoryStream对象
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(ms);
bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);//保存到服务器路径
ms.Close();//关闭当前流,并释放所有与之关联的资源
return Content(Net.Url + virtualPath); //返回文件路径
}
结语
由于项目实际情况,上述的方案中还存在诸多未完善的点:
1、多张图片上传,还是采用的与单张图片相同的接口处理, 更为完善的方案是,前端的多图上传组件只绑定一个关联Id,即可通过实现上传和将图片列表查询展示(注:该功能在微信端未实现)。
2、后端图片上传的接口,未做严格的安全校验,更为完善的方案是,每个上传的场景,都应该限制文件类型,限制文件大小,以及文件数据来源校验(注: 如软件需要按二级等保标准测评,则后端接口会检测通不过)。
3、上传组件,未显示上传进度,体验性稍差。
正如前文所述,出于项目实际情况考虑,只是简单实现图片压缩上传功能,如要支持更多的场景,还得细细雕琢。
参考
1、移动端H5实现图片上传
2、安卓版微信 input onchange事件不生效
基于vue + axios + lrz.js 微信端图片压缩上传的更多相关文章
- 纯原生js移动端图片压缩上传插件
前段时间,同事又来咨询一个问题了,说手机端动不动拍照就好几M高清大图,上传服务器太慢,问问我有没有可以压缩图片并上传的js插件,当然手头上没有,别慌,我去网上搜一搜. 结果呢,呵呵...诶~又全是基于 ...
- 基于H5+ API手机相册图片压缩上传
// 母函数 function App(){} /** * 图片压缩,默认同比例压缩 * @param {Object} path * pc端传入的路径可以为相对路径,但是在移动端上必须传入的路径是照 ...
- js 图片压缩上传(base64位)以及上传类型分类
一.input file上传类型 1.指明只需要图片 <input type="file" accept='image/*'> 2.指明需要多张图片 <input ...
- 三款不错的图片压缩上传插件(webuploader+localResizeIMG4+LUploader)
涉及到网页图片的交互,少不了图片的压缩上传,相关的插件有很多,相信大家都有用过,这里我就推荐三款,至于好处就仁者见仁喽: 1.名气最高的WebUploader,由Baidu FEX 团队开发,以H5为 ...
- Html5+asp.net mvc 图片压缩上传
在做图片上传时,大图片如果没有压缩直接上传时间会非常长,因为有的图片太大,传到服务器上再压缩太慢了,而且损耗流量. 思路是将图片抽样显示在canvas上,然后用通过canvas.toDataURL方法 ...
- 分享图片压缩上传demo,可以选择一张或多张图片也可以拍摄照片
2016-08-05更新: 下方的代码是比较OLD的了,是通过js进行图片的剪切 旋转 再生成,效率较低. 后来又整合了一个利用native.js本地接口的压缩代码 ,链接在这 .页面中有详细的说明, ...
- springMVC多图片压缩上传的实现
首先需要在配置文件中添加配置: <!--配置文件的视图解析器,用于文件上传,其中ID是固定的:multipartResolver--> <bean id="multipar ...
- 基于HTML5的可预览多图片Ajax上传
一.关于图片上传什么什么的 在XHTML的时代,我们使用HTML file控件上传图片一次只能上传一张.要一次上传多图,做法是借助于flash.例如swfupload.js.可惜,使用复杂的点,比如f ...
- Vue directive自定义指令+canvas实现H5图片压缩上传-Base64格式
前言 最近优化项目-手机拍照图片太大,回显速度比较慢,使用了vue的自定义指令实现H5压缩上传base64格式的图片 canvas自定义指令 Vue.directive("canvas&qu ...
随机推荐
- typescript项目配置路径别名(路径映射)
在vue项目中,我们可以利用“@”来指代src目录,在普通webpack项目中,我们也可以通过配置webpack的config来指定路径别名,但是在typescript+webpack项目中我们该怎么 ...
- (1)OracleClient数据库操作(淘汰)
一.数据库连接 Oracle 数据提供程序,位于System.Data.OracleClient 命名空间.( .NET 4 以后的版本,会将不在维护和更新了) 第一步:引入命名空间 在程序的开头写上 ...
- Python与数据库[1] -> 数据库接口/DB-API[3] -> ODBC 适配器
ODBC适配器 / ODBC Adaptor ODBC(Open Database Connectivity,开放数据库互连)是微软公司开放服务结构(WOSA,Windows Open Service ...
- ACM集训日志——day1——15.7.8
UVA 11292 The Dragon of Loowater 题意 给n个头,m个骑士,骑士有能力值x,代表他可以砍掉一个直径不超过x的头,并且佣金为x,求要砍掉所有的头,需要的最少佣金是多少. ...
- (转)Unity3D 开发优秀技术资源汇总
原文:http://www.j2megame.com/html/xwzx/ty/3179.html Unity3D 博客 http://www.dapp.com.br/ by Dapp http:/ ...
- 磁盘爆满导致MySQL无法启动:Disk is full writing './mysql-bin.~rec~' (Errcode: 28). Waiting for someone to free space...
今天收到监控邮件说博客访问失败.打开页面一看,硕大的502 Bad Gateway,ping了一下VPS发现是通的,SSH连接上去看了下Nginx日志发现没问题,重启lnmp的时候发现Mysql起不来 ...
- Sql性能检测工具:Sql server profiler和优化工具:Database Engine Tuning Advisor
原文:Sql性能检测工具:Sql server profiler和优化工具:Database Engine Tuning Advisor 一.工具概要 数据库应用系统性能低下,需要对其进行优化 ...
- DTU
DTU 编辑 DTU (Data Transfer unit),是专门用于将串口数据转换为IP数据或将IP数据转换为串口数据通过无线通信网络进行传送的无线终端设备.DTU广泛应用于气象.水文水利.地质 ...
- NSPredicate谓词查询
Cocoa提供了一个类NSPredicate类,该类主要用于指定过滤器的条件,该对象可以准确的描述所需条件,对每个对象通过谓词进行筛选,判断是否与条件相匹配.谓词表示计算真值或假值的函数. NSPre ...
- Linux文件压缩与解压命令
1 .zip 格式压缩与解压 压缩命令 zip 压缩文件名 源文件 zip -r 压缩目录名 源目录 解压命令 unzip 文件名 td@td-Lenovo-IdeaPad-Y41 ...