Canvas 使用 toDataUrl 导出图片的各类错误

前不久接到一个活动需求,用户拍照然后结合相应的素材生成另一张图片并分享传播。因为个人对 Canvas 很感兴趣就兴致勃勃的接下来了,后期遇到很多有意思的坑,分享一下。

// 绘制上下文获取
var doc = document
var can = doc.getElementById('canvas')
var ctx = can.getContext('2d')

首先是获取用户的图片文件,这里使用 input 标签获取

// Html:
<input type="file" class="js_photo_input" id="input" accept="image/*" multiple style="display: none;"/>
<div id="input"></div>

// 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

在HTML5中,`<canvas>`元素提供了一个`toDataURL()`方法,该方法可以将canvas画布的内容导出图片格式。默认情况下,生成的图片是PNG格式的,但如果要压缩图片,需要使用一个压缩库,因为`toDataURL()`方法本身并不提供压缩功能。 要压缩PNG图片,可以使用第三方JavaScript库,例如`image-png-compressor`或`TinyPNG`的API。以下是一个使用假设的第三方库`compressPNG`来压缩图片的示例: ```javascript // 假设canvas已经绘制好了内容 var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); // 使用第三方库compressPNG来压缩canvas导出图片数据 var imgData = canvas.toDataURL('image/png'); compressPNG(imgData, function(compressedDataUrl) { // 这里的compressedDataUrl就是压缩后的图片的DataURL console.log(compressedDataUrl); // 可以将压缩后的DataURL用于图片标签或任何其他用途 var img = document.createElement('img'); img.src = compressedDataUrl; document.body.appendChild(img); }, function(error) { // 错误处理 console.error(error); }); // compressPNG函数可能看起来像这样: // 这是一个示例函数,实际使用时需要替换成实际可用的压缩库函数 function compressPNG(dataURL, successCallback, errorCallback) { // 这里可以使用Ajax调用服务器端的压缩服务或者客户端的压缩库来实现压缩 // 假设我们调用了一个第三方服务的API fetch('https://api.somecompressservice.com/compress', { method: 'POST', body: JSON.stringify({ dataURL: dataURL }), headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.success) { successCallback(data.compressedDataURL); } else { errorCallback(data.error); } }) .catch(error => { errorCallback(error); }); } ``` 请注意,上面的代码仅是一个示例,实际中需要使用真实有效的压缩服务或库,并且遵循该服务或库的API文档进行操作。另外,跨域请求可能需要服务器端的支持。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值