手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件

前言

今天我们要讲解下如何编写一个图片压缩、方向纠正插件,附带着会讲解下如何上传和预览。

为什么重点放在图片压缩和方向纠正?

相信大家在做项目过程中,经常会遇到上传图片到后端,但是由于图片过大,需要对图片压缩处理。特别在移动端,手机拍的照片普遍过于大了,我们有时候只是需要上传一张头像,很小就够用了。还有在部分手机上(已知苹果手机)拍的照片存在方向角度问题,这时就需要我们来纠正图片角度了。

很多同学多数时候是在用别人写好的图片压缩上传插件。针对我们的需求,这些插件有时候不能达到我们最理想的效果,自己写呢,又不会写,很是头疼。今天就深入剖析讲解下,教会大家编写自己的图片压缩、方向纠正插件,以及预览和上传压缩后图片数据。

文中用到的一些H5的api和EXIF.js等知识点如果不懂的话,请先阅读文末尾的结语中的知识点资料。


实现原理

压缩图片并且上传主要用到filereader、canvas 以及 formdata 这三个h5的api和插件EXIF.js。逻辑并不难。整个过程就是:

  (1)用户使用input file上传图片的时候,用filereader读取用户上传的图片数据(base64格式)

  (2)把图片数据传入img对象,然后将img绘制到canvas上,用EXIF.js对图片方向进行纠正,再调用canvas.toDataURL对图片进行压缩,获取到压缩后的base64格式图片数据,转成二进制

  (3)获取到压缩后的图片二进制数据,预览。

  (4)将压缩后的图片二进制数据塞入formdata,再通过XmlHttpRequest提交formdata

  如此四步,就完成了图片的压缩、方向纠正、预览和上传。


插件设计思考

考虑到在实际项目中,可能用不同的开发框架(vue.js/JQ/react.js/angular.js/anu.js等),图片预览的UI样式也可能不同,图片数据上传方法可能不同。因为图片压缩和方向纠正这两块的逻辑多变性比较低,我们这里把图片压缩和方向纠正抽离出来,封装为一个插件库。


【一】获取图片数据

先是获取图片数据,也就是监听input file的change事件,然后获取到用来压缩上传的文件对象files,将files传到【图片压缩、方向纠正插件】中进行处理。

这时候根据每个人的需求,也可以预览未压缩的图片。

//监听上传组件input的onchange事件,压缩图片,纠正图片方向,同时获取压缩后的图片
filechooser.onchange = function () {
    var fileList = this.files; //预览压缩前的图片 var files = Array.prototype.slice.call(fileList); files.forEach(function (file, i) { var reader = new FileReader(); reader.onload = function () { var li = document.createElement("li") li.style.backgroundImage = 'url('+this.result+')'; document.querySelector('.img_list').appendChild(li) } reader.readAsDataURL(file); }); //处理图片列表,getCompressiveFileList接受处理后的图片数据列表 //下面两行代码为图片压缩、方向纠正插件的用法,具体实现细节请继续往下阅读 ~_~ ↓↓↓ var process = window.lzImgProcess(); process(fileList, getCompressiveFileList); } 

【二】图片压缩、方向纠正插件实现

上面做完图片数据的获取后,就可以做process压缩图片的方法了。而压缩图片也并不是直接把图片绘制到canvas再调用一下toDataURL就行的。

  在IOS中,canvas绘制图片是有两个限制的:

  首先是图片的大小,如果图片的大小超过两百万像素,图片也是无法绘制到canvas上的,调用drawImage的时候不会报错,但是你用toDataURL获取图片数据的时候获取到的是空的图片数据。

  再者就是canvas的大小有限制,如果canvas的大小大于大概五百万像素(即宽高乘积)的时候,不仅图片画不出来,其他什么东西也都是画不出来的。

  应对上面两种限制,我把图片宽度、高度压缩控制在1000px以内,这样图片最大就不超过两百万像素了。在前端开发中,1000px*1000px基本可以满足绝大部分的需求了。当然了还有更完美的瓦片式绘制的方法,我们这里就说瓦片式绘制方法了。

  如此一来就解决了IOS上的两种限制了。

  除了上面所述的限制,还有两个坑,一个就是canvas的toDataURL是只能压缩jpg的(这句话的详细解释可以看下面的Tip讲解),当用户上传的图片是png的话,就需要转成jpg,也就是统一用canvas.toDataURL('image/jpeg', 0.5) , 类型统一设成jpeg,而压缩比就自己控制了。

  另一个就是如果是png转jpg,绘制到canvas上的时候,canvas存在透明区域的话,当转成jpg的时候透明区域会变成黑色,因为canvas的透明像素默认为rgba(0,0,0,0),所以转成jpg就变成rgba(0,0,0,1)了,也就是透明背景会变成了黑色。解决办法就是绘制之前在canvas上铺一层白色的底色。

  在压缩图片之前,我们判断图片角度,如果图片角度不正确,还需要用EXIF.js把图片角度纠正过来。

  压缩完图片,把base64的图片数据转成二进制数据存储到暂存区中,等待被getBlobList获取使用。

Tip: canvas的toDataURL是只能压缩jpg这句话我可能说的不清楚,我想表达的意思是这个api无论是jpeg还是png,最后导出的时候,都跟jpeg没啥差别了。因为png图片的透明性质在canvas中是无效的,会被canvas添加默认的黑色背景,我在文中讲解的时候,用白色背景处理了,所以最后导出的图片,无论你设置的是png还是jpeg都跟jpeg没啥区别了,因为无法保持png的透明度性质了

(function(window) {

    /**
     * 
     * 作者:混沌传奇
     * 
     * 邮箱地址:iot-pro_lizeng@foxmail.com
     * 
     * 日期:2017-10-26
     * 
     * 插件功能:压缩图片&&纠正图片方向&&返回二进制(Blob)图片元数据组成的列表
     * 
     */

    window.lzImgProcess = function () { var Orientation = '', //图片方向角 blobList = [], //压缩后的二进制图片数据列表 canvas = document.createElement("canvas"), //用于压缩图片(纠正图片方向)的canvas ctx = canvas.getContext('2d'), file_type = 'image/jpeg', //图片类型 qlty = 0.5, //图片压缩品质,默认是0.5,可选范围是0-1的数字类型的值,可配置 imgWH = 1000; //压缩后的图片的最大宽度和高度,默认是1000px,可配置 /** * @actionName process, * 方法功能:压缩图片&&纠正图片方向&&返回二进制(Blob)图片元数据 * * @param fileList,传入函数的文件列表对象,fileList对象是来自用户在一个<input>元素上选择文件后返回的FileList对象 * 注意:图片类型必须是jpeg||png * 比如:<input id="uploadImage" onchange="loadImageFile();" /> * function loadImageFile() { * //获取返回的fileList对象 * var fileList = document.getElementById("uploadImage").files; * } * @param getBlobList [Blob],获取压缩结果的钩子函数,接受一个参数。 * 功能:在图片压缩完毕后,获取压缩后的二进制图片数据对象组成的数组,参数即:压缩后的二进制图片数据(blob)组成的list * * @param quality,传入函数的图片压缩比率(品质),可选范围0-1的数字类型的值,默认是0.5 * * @param WH,传入函数的图片压缩后的最大图片宽度和高度,默认是1000,单位是px,可自由配置。 * 注意:最好不要超过1000,数字过大,容易导致canvas压缩失败。由于没做瓦片处理,所以有这个限制。1000*1000的图片在前端中,基本也够用了。 * */ function process (fileList, getBlobList, quality, WH) { blobList = []; //初始化blobList // 判断参数fileList的长度是否大于0 if (!fileList.length){ console.log('警告:传进方法process的参数fileList长度必须大于零!!!') return; } //如果quality参数有值,则把quality赋值给qlty(图片压缩的品质) if(quality) qlty = quality; //如果WH参数有值,则把WH赋值给imgWH(压缩后的图片的最大宽度和高度) if(WH&&WH<1000&&WH>0){ imgWH = WH; } // 把传进来的fileList转为数组类型 var files = Array.prototype.slice.call(fileList); files.forEach(function (file, i) { if (!/\/(?:jpeg|png)/i.test(file.type)){ console.log('警告:图片必须是jpeg||png类型!!!'); return; } // file_type = file.type; var reader = new FileReader(); // 获取图片压缩前大小,打印图片压缩前大小 var size = file.size/1024 > 1024 ? (~~(10*file.size/1024/1024))/10 + "MB" : ~~(file.size/1024) + "KB"; // console.log('size:', size) reader.onload = function () { var img = new Image(); img.src = this.result; // 图片加载完毕之后进行压缩 if (img.complete) { callback(); } else { img.onload = callback; } function callback() { //获取照片方向角属性,用户旋转控制 EXIF.getData(img, function() { // alert(EXIF.pretty(this)); EXIF.getAllTags(this); // alert(EXIF.getTag(this, 'Orientation')); Orientation = EXIF.getTag(this, 'Orientation'); if(Orientation == ""||Orientation == undefined||Orientation == null){ Orientation = 1; } }); //获取压缩后的图片二进制数据 var data = GetImgCompress(img); //将二进制数据塞入到二进制数据列表中 blobList.push(data); //将压缩后的二进制图片数据对象(blob)组成的list通过钩子函数返回出去 if(blobList.length===files.length){ if(getBlobList) getBlobList(blobList); } img = null; } }; reader.readAsDataURL(file); }) } /** * @actionName GetImgCompress, * 功能:判断上传图片的方向,如果不是正确的,进行修正,并对图片进行压缩,压缩完后,返回压缩后的二进制图片数据 * * @param img, 用来压缩的图片对象 * * @returns 返回的压缩后的二进制图片数据 */ function GetImgCompress(img){ if (navigator.userAgent.match(/iphone/i)) { //console.log('iphone'); //如果方向角不为1,都需要进行旋转 if(Orientation != "" && Orientation != 1){ switch(Orientation){ case 6://需要顺时针(向左)90度旋转 rotateImg(img,'left',canvas); break; case 8://需要逆时针(向右)90度旋转 rotateImg(img,'right',canvas); break; case 3://需要180度旋转 rotateImg(img,'right',canvas);//转两次 rotateImg(img,'right',canvas); break; } }else{ //不做旋转 rotateImg(img,'no',canvas); } }else if (navigator.userAgent.match(/Android/i)) {// 修复android if(Orientation != "" && Orientation != 1){ switch(Orientation){ case 6://需要顺时针(向左)90度旋转 rotateImg(img,'left',canvas); break; case 8://需要逆时针(向右)90度旋转 rotateImg(img,'right',canvas); break; case 3://需要180度旋转 rotateImg(img,'right',canvas);//转两次 rotateImg(img,'right',canvas); break; } }else{ //不做旋转 rotateImg(img,'no',canvas); } }else{ if(Orientation != "" && Orientation != 1){ switch(Orientation){ case 6://需要顺时针(向左)90度旋转 rotateImg(img,'left',canvas); break; case 8://需要逆时针(向右)90度旋转 rotateImg(img,'right',canvas); break; case 3://需要180度旋转 rotateImg(img,'right',canvas);//转两次 rotateImg(img,'right',canvas); break; } }else{ //不做旋转 rotateImg(img,'no',canvas); } } var ndata; ndata = canvas.toDataURL(file_type, qlty); //打印压缩前后的大小,以及压缩比率 // var initSize = img.src.length; // console.log('压缩前:' + initSize); // console.log('压缩后:' + ndata.length, 'base64数据', ndata); // console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%"); //将压缩后的base64数据转为二进制数据 ndata = dataURItoBlob(ndata); //清除canvas画布的宽高 canvas.width = canvas.height = 0; return ndata; } /** * @actionName rotateImg, * 功能:对图片旋转处理 * * @param img, 用来矫正方向的图片对象 * * @param direction, 旋转方向 * * @param canvas, 用来绘制图片的cavas画布对象 */ function rotateImg(img, direction,canvas) { //最小与最大旋转方向,图片旋转4次后回到原方向 var min_step = 0; var max_step = 3; if (img == null)return; //img的高度和宽度不能在img元素隐藏后获取,否则会出错 var height = img.height; var width = img.width; if(width>imgWH || height>imgWH){ var ratio = ~~(height/width*10)/10; if(width>height){ width = imgWH; height = imgWH*ratio; }else{ height = imgWH; width = height/ratio; } img.width = width; img.height = height; } canvas.width = width; canvas.height = height; // 铺底色 ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, width, height); var step = 2; if (step == null) { step = min_step; } if (direction == 'no'){ step = 0; } else if (direction == 'right') { step++; //旋转到原位置,即超过最大值 step > max_step && (step = min_step); } else { step--; step < min_step && (step = max_step); } //旋转角度以弧度值为参数 var degree = step * 90 * Math.PI / 180; switch (step) { case 0: ctx.drawImage(img, 0, 

转载于:https://www.cnblogs.com/lvhw/p/8507201.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
通达信是一款广受中国投资者喜爱的股票行情分析软件,而编写通达信的插件可以帮助用户更方便地进行股票分析和决策。下面是一个图文手把手你如何使用VC 2010编写通达信缠论插件的步骤。 第一步:准备工作 1. 确保已经安装好Visual Studio 2010和通达信软件。 2. 打开Visual Studio 2010,创建一个新的C++项目。 第二步:导入相关文件 1. 在项目中右键单击“资源文件”,选择“添加->现有项”。 2. 导航到通达信的安装目录,找到插件相关的头文件和库文件,选择并导入到项目中。 第三步:编写代码 1. 在项目中创建一个新的C++源文件,命名为“plugin.cpp”。 2. 在“plugin.cpp”中编写你的插件代码,可以包括初始化、数据处理、绘图等功能。 第四步:编译和调试 1. 点击Visual Studio 2010的“生成->生成解决方案”菜单,编译你的插件代码。 2. 将编译生成的插件文件(例如“plugin.dll”)复制到通达信的插件目录中。 3. 启动通达信软件,并在菜单栏中找到你的插件,选择并运行它。 第五步:测试和验证 1. 在通达信软件中打开一个股票行情页面,确保你的插件正确运行。 2. 测试你的插件是否能够按照你的预期显示或处理数据。 3. 如果发现错误或需要改进,返回Visual Studio 2010,修改代码后重新编译和调试。 通过以上步骤,你就可以使用VC 2010编写通达信缠论插件了。当然,这只是一个简单的程示例,实际的插件编写可能涉及更复杂的功能和算法。希望这个回答能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值