昨天在吃饭过程中,和同事聊到canvas跨域的的限制,我一开始以为canvas所有的API接口操作不同域的图片都会跨域,但是这是一个错误的想法,比如使用drawImage()方法将图片渲染在canvas中是没有跨域的限制的。
在了解跨域的时候,我们首先要搞清楚什么是浏览器的同源策略。
同源的定义
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源,
我以下面的表举例说明
那对于跨域的问题,我们怎们去解决呢,在之前两个不同域请求接口的时候,xmlhttprequest因为同源策略的限制导致不能跨域请求。于是利用script标签可跨域,并能够执行返回js脚本的功能,我们利用这个来解决跨域请求的问题,也就是jsonp,但是这种操作有个问题
- 只支持GET请求
- 不能获取http的各种状态
- 安全问题,如果站点不可信,XSS攻击是家常便饭
- 没有超时机制
那怎么办,W3C组织提出CORS规范(Cross-Origin Resource Sharing )从2004年提出到现在的版本规范时间是2014年。于是现在基本上ajax和fetch 都支持cors
浏览器的支持情况如下
在跨域请求的时候,我们在响应头中添加Access-Control-Allow-Origin 字段,Access-Control-Allow-Origin 一般设置允许的请求源,如Access-Control-Allow-Origin:www.baidu.com 或设置成 Access-Control-Allow-Origin :* 支持所有站点请求。
但是设置了access-control-allow-origin 为* 就行了吗,有时候我们只允许post 方法跨域,有时候我们在使用xmlhttprequest 或fetch 的时候,我们需要添加一些字段,但是服务那么允许我们添加哪些头,我们是不知道的。
这样跨域请求分为两种,一直是简单跨域请求(Simple Cross-Origin Request),另一种是预检请求(Preflight Request) 。预检请求是一种保护机制,浏览器会在发送实际请求发送一个OPTIONS 请求来判断服务器是否能够接受该跨域请求,OPTIONS请求主要起到探针的作用。
简单跨域请求
当我们的请求同时满足以下所有条件时,不需要进行预检请求
- 1.使用下列方法之一
GET
HEAD
POST
- Fetch规范规定的对CORS安全的首部字段集合
Accept
Accept-Language
Content-Language
Content-Type (值为text/plain multipart/form-data application/x-www-form-urlencoded)
DPR
Downlink
Save-Data
Viewport-Width
Witdth
- 请求中没有使用
ReadableStream
对象。 - 请求中的任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器;XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问。
如果请求没有满足上述条件,那么会进行预检请求。
预检请求的过程大致如下
跨域资源共享机制就大致说到这里,我们然后回到canvas中使用getImageData碰到场景。
我们模拟两个站点,一个是 aa.com站点,一个是bb.com站点。在hosts文件设置域名映射
127.0.0.1 aa.com
127.0.0.1 bb.com
demo视频
代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>canvas测试</title>
</head>
<body>
<canvas width="400" height="600" id="canvas-test">
</canvas>
<script>
(function(){
'use strict'
const canvas = document.getElementById("canvas-test")
const canvasContext = canvas.getContext('2d')
const img = new Image()
img.src="https://s.vipkidstatic.com/fe-static/mobile/learning-effect/prd/src/page/prd/img/common/bg@2x-2e6bf90fab2d77144cec052e5aa1766e.png"
img.crossOrigin="" // "" 等价于Anonymous
img.onload= ()=>{
canvasContext.drawImage(img, 0, 0, 400, 600)
canvasContext.getImageData(0,0,40,40)
}
}())
</script>
</body>
</html>
从视频中我们可以看到使用drawImage是没有跨域的的限制的,但是使用getImageData()方法有跨域的限制
如下图所示
这个时候我们设置了 img.crossOrigin=”” (””等价于Anonymous)也是不行。那么我们找一张支持跨域的图片。
这个时候没有报错,我们可以总结如下:
在canvas中的getImageData(),toBlob() ,toDataURL()方法 对跨域图片操作是除了需要将crossOrigin 属性值设置为anonymous 之外,并且跨域的图片的的响应头字段acess-control-allow-origin 需要进行设置,这样才能在大部分浏览器中不会报跨域错误。
但是在safari浏览器中,并不支持,会报安全问题。
Safari报 SecurityError: The operation is insecure. 那么有没有其他的解决方式。我们通过xmlhttpRequest和URL.createObjectURL()的来解决这个问题。代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>canvas测试</title>
</head>
<body>
<canvas width="400" height="600" id="canvas-test">
</canvas>
<script>
(function () {
const xhr = new XMLHttpRequest();
const canvas = document.getElementById("canvas-test")
const canvasContext = canvas.getContext('2d')
xhr.onload = function () {
const url = URL.createObjectURL(this.response);
const img = new Image()
img.onload = function () {
canvasContext.drawImage(img,0,0,400,600)
canvasContext.getImageData(0,0,100,100)
// 图片用完后记得释放内存
URL.revokeObjectURL(url)
}
img.src = url
}
xhr.open('GET', 'https://s.vipkidstatic.com/fe-static/mobile/learning-effect/prd/src/page/prd/img/common/bg@2x-2e6bf90fab2d77144cec052e5aa1766e.png', true)
xhr.responseType = 'blob'
xhr.send()
}())
</script>
</body>
</html>
safari这个问题可以通过上述方案解决,但是也要必须保证图片所在服务设置了acess-control-allow-origin:* 。
参考文档:
发表评论