💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 概述 在IOS中,canvas绘制图片是有两个限制的: 首先是图片的大小,如果图片的大小超过两百万像素,图片也是无法绘制到canvas上的,调用drawImage的时候不会报错,但是你用toDataURL获取图片数据的时候获取到的是空的图片数据。 再者就是canvas的大小有限制,如果canvas的大小大于大概五百万像素(即宽高乘积)的时候,不仅图片画不出来,其他什么东西也都是画不出来的。 应对上面两种限制,我把图片宽度、高度压缩控制在1000px以内,这样图片最大就不超过两百万像素了。在前端开发中,**1000px * 1000px**基本可以满足绝大部分的需求了。   除了上面所述的限制,还有两个坑,一个就是canvas的toDataURL是只能压缩jpg的,当用户上传的图片是 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上铺一层白色的底色。 ~~~ ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, width, height); ~~~ 在压缩图片之前,我们判断图片角度,如果图片角度不正确,还需要用EXIF.js把图片角度纠正过来。 压缩完图片,把base64的图片数据转成二进制数据存储到暂存区中,等待被getBlobList获取使用。 <br> ## Orientation 在手机上通过网页 input 标签拍照上传图片,有一些手机会出现图片旋转了90度d的问题,包括 iPhone 和个别三星手机。这些手机竖着拍的时候才会出现这种问题,横拍出来的照片就正常显示。因此,可以通过获取手机拍照角度来对照片进行旋转,从而解决这个问题。 这个参数并不是所有图片都有的,不过手机拍出来的图片是带有这个参数的。 | 旋转角度 | 参数值 | | --- | --- | | 0° | 1 | | 顺时针90° | 6 | | 逆时针90° | 8 | | 180° | 3 | 参数为 1 的时候显示正常,那么在这些横拍显示正常,即 Orientation = 1 的手机上,竖拍的参数为 6。 exif.js 获取 Orientation : ~~~ EXIF.getData(file, function() { var Orientation = EXIF.getTag(this, 'Orientation'); }); ~~~ <br> ## 限制宽高 ~~~ 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,可配置 ~~~ ~~~ //如果WH参数有值,则把WH赋值给imgWH(压缩后的图片的最大宽度和高度) if(WH&&WH<1000&&WH>0){ imgWH = WH; } ~~~ ~~~ // 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 } ~~~ <br> ## 设置白色背景 ~~~ // 设置为白色背景,jpg是不支持透明的,所以会被默认为canvas默认的黑色背景 that.ctx.fillStyle = '#fff' that.ctx.fillRect(0, 0, canvas.width, canvas.height); ~~~ <br> ## 旋转 旋转需要用到 canvas 的 rotate() 方法。 ~~~ ctx.rotate(angle); ~~~ rotate 方法的参数为旋转弧度。需要将角度转为弧度:degrees * Math.PI / 180 旋转的中心点默认都在 canvas 的起点,即 ( 0, 0 )。旋转的原理如下图: ![](https://box.kancloud.cn/e322aa33ee602d761cc28de03c7ab852_700x355.png) 旋转之后,如果从 ( 0, 0 ) 点进行 drawImage(),那么画出来的位置就是在左图中的旋转 90 度后的位置,不在可视区域呢。旋转之后,坐标轴也跟着旋转了,想要显示在可视区域呢,需要将 ( 0, 0 ) 点往 y 轴的反方向移 y 个单位,此时的起始点则为 ( 0, -y )。 同理,可以获得旋转 -90 度后的起始点为 ( -x, 0 ),旋转 180 度后的起始点为 ( -x, -y )。 <br> ## 压缩 HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。 * 如果画布的高度或宽度是0,那么会返回字符串“data:,”。 * 如果传入的类型非“image/png”,但是返回的值以“data:image/png”开头,那么该传入的类型是不支持的。 * Chrome支持“image/webp”类型。 语法 ~~~ canvas.toDataURL(type, encoderOptions) ~~~ type:可选,图片格式,默认为 image/png。 encoderOptions:可选,在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。 这里我们使用 ~~~ canvas.toDataURL("image/jpeg", 0.5); ~~~ <br> ## 完整逻辑 ~~~ Lrz.prototype._createBase64 = () { var that = this, resize = that.resize, img = that.img, canvas = that.canvas, ctx = that.ctx, defaults = that.defaults, orientation = that.orientation; // 调整为正确方向 switch (orientation) { case 3: ctx.rotate(180 * Math.PI / 180); ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height); break; case 6: ctx.rotate(90 * Math.PI / 180); ctx.drawImage(img, 0, -resize.width, resize.height, resize.width); break; case 8: ctx.rotate(270 * Math.PI / 180); ctx.drawImage(img, -resize.height, 0, resize.height, resize.width); break; case 2: ctx.translate(resize.width, 0); ctx.scale(-1, 1); ctx.drawImage(img, 0, 0, resize.width, resize.height); break; case 4: ctx.translate(resize.width, 0); ctx.scale(-1, 1); ctx.rotate(180 * Math.PI / 180); ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height); break; case 5: ctx.translate(resize.width, 0); ctx.scale(-1, 1); ctx.rotate(90 * Math.PI / 180); ctx.drawImage(img, 0, -resize.width, resize.height, resize.width); break; case 7: ctx.translate(resize.width, 0); ctx.scale(-1, 1); ctx.rotate(270 * Math.PI / 180); ctx.drawImage(img, -resize.height, 0, resize.height, resize.width); break; default: ctx.drawImage(img, 0, 0, resize.width, resize.height); } return new Promise(function (resolve) { if (UA.oldAndroid || UA.mQQBrowser || !navigator.userAgent) { require(['jpeg_encoder_basic'], function (JPEGEncoder) { var encoder = new JPEGEncoder(), img = ctx.getImageData(0, 0, canvas.width, canvas.height); resolve(encoder.encode(img, defaults.quality * 100)); }) } else { resolve(canvas.toDataURL('image/jpeg', defaults.quality)); } }); }; ~~~ <br> ## 判断选择方向 <br> ## base64转Blob ~~~ /** * dataURL to blob, ref to https://gist.github.com/fupslot/5015897 * @param dataURI,图片的base64格式数据 * @returns {Blob} */ function dataURItoBlob(dataURI) { var byteString = atob(dataURI.split(',')[1]); var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; var ab = new ArrayBuffer(byteString.length); var ia = new Uint8Array(ab); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ab], {type: mimeString}); } ~~~ <br> ## Exif.js Exif.js 提供了 JavaScript 读取图像的原始数据的功能扩展,例如:拍照方向、相机设备型号、拍摄时间、ISO 感光度、GPS 地理位置等数据。 <br> ## 参考资料 [localResizeIMG](https://github.com/think2011/localResizeIMG) [手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件 - 掘金](https://juejin.im/post/5a9759a16fb9a0635b5360b3) [移动端图片上传旋转、压缩的解决方案](https://zhuanlan.zhihu.com/p/27627436)