前不久接到一个活动需求,用户拍照然后结合相应的素材生成另一张图片并分享传播。因为个人对 Canvas 很感兴趣就兴致勃勃的接下来了,后期遇到很多有意思的坑,分享一下。
// 绘制上下文获取
var doc = document
var can = doc.getElementById('canvas')
var ctx = can.getContext('2d')
首先是获取用户的图片文件,这里使用 input 标签获取
// Html:
// JS
// 因为 input 的样式是不能重写的,这里使用一个 div 绑定 click 事件去触发文件选择
doc.getElementById('input').click()
// 绑定事件,监听文件获取
$('.js_photo_input').on('change', fileChanged)
这里有一个需要注意的点,iPhone 下竖屏拍照,系统会自动将图片旋转,导致输出的图片与拍照时显示不一致。这里需要使用相关的接口处理一下。
详细介绍
// 获取用户选择的文件
function fileChanged(e) {
var files = e.target.files
if(files[0]){
// 获取文件后使用 getOrientation 函数处理
getOrientation(files[0], function(orientation) {
// orientation 是图片的旋转系数,参考下面的图
// 一般情况下 iPhone 对图片的旋转为下图的情况 6
// ctx.rotate(90 * Math.PI / 180) 旋转90度
// 然后使用 canvas 处理,导出新的图片即可
})
return fileHandleShow(files[0])
}
}
function getOrientation(file, callback) {
// iphone 下图片被系统旋转
var reader = new FileReader()
reader.onload = function(e) {
var view = new DataView(e.target.result)
if (view.getUint16(0, false) != 0xFFD8) return callback(-2)
var length = view.byteLength, offset = 2
while (offset < length) {
var marker = view.getUint16(offset, false)
offset += 2
if (marker == 0xFFE1) {
if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1)
var little = view.getUint16(offset += 6, false) == 0x4949
offset += view.getUint32(offset + 4, little)
var tags = view.getUint16(offset, little)
offset += 2
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + (i * 12), little) == 0x0112)
return callback(view.getUint16(offset + (i * 12) + 8, little))
}
else if ((marker & 0xFF00) != 0xFF00) break
else offset += view.getUint16(offset, false)
}
return callback(-1)
};
reader.readAsArrayBuffer(file.slice(0, 64 * 1024))
}
然后到了绘制步骤,图片必须加载完成后才可以绘制,否则绘制不出图像,不多说。
var img = new Image()
img.src = 'url'// 图片的地址
img.onload = function() {
var _this = this
ctx.drawImage(_this, 0, 0, 100, 100)
}
在 iPhone 中,用户拍照的图片动辄上10M,再经过 canvas 处理为 base64 格式,体积非常大,所以需要使用 canvas 压缩处理。
var img = new Image()
img.src = 'url' // 图片的地址
img.onloda = function() {
var quality = 0.5 // 压缩系数,默认是 0.92 范围 0 ~ 1
//生成canvas
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
var w = that.width, h = that.height
var anw = document.createAttribute("width")
anw.nodeValue = w
var anh = document.createAttribute("height")
anh.nodeValue = h
canvas.setAttributeNode(anw)
canvas.setAttributeNode(anh)
ctx.drawImage(that, 0, 0, w, h)
}
var base64 = canvas.toDataURL('image/jpeg', quality) // 这里得到压缩后的 base64 格式的图片
接下来使用 drawImage 绘制图片,使用 toDataUrl 导出。导出的时候要注意,如果有跨域图片资源,会报错。由于我们的项目资源都是放在 CDN 的,所以有跨域的问题,导致导出图片失败。
// 错误信息
Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported
// 大致意思是,画布已经被污染,无法导出
// 这里是因为 canvas 在安全方面做了限制
// 解决方案有两种
// 第一种就是把图片放在本域下,这样就没有跨域问题了
// 第二种方案是,存放图片的服务器进行配置
// 或者参数只配置页面的域名
Access-Control-Allow-Origin="*"
// 然后在导出的图片设置相关属性
var image = new Image()
image.setAttribute('crossOrigin','anonymous') // 设置属性 crossOrigin
image.src = can.toDataURL('image/jpeg', 0.6)
// 然后就可以导出了
// 两种解决方法都有缺陷,视情况选择
然后就可以正确导出了,这里图片一般都是要经过服务器处理存储的,如果图片过大可能会导致图片上传损坏,所以对图片的压缩处理很重要。
// 在 URL 后面加一个时间戳就不会报错了,也不需要服务端设置
// 当然了,这样的话 cdn 也会失效,没有缓存作用
var img = new Image();
var timestamp = new Date().getTime();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url + '?' + timestamp;
详细链接
原文链接:https://blog.csdn.net/qq_25243451/article/details/79758132