not a better man

前端技术

canvas中getImageData(),toBlob(),toDataURL()跨域问题的解决

井冈山会师

井冈山会师

 

昨天在吃饭过程中,和同事聊到canvas跨域的的限制,我一开始以为canvas所有的API接口操作不同域的图片都会跨域,但是这是一个错误的想法,比如使用drawImage()方法将图片渲染在canvas中是没有跨域的限制的。

在了解跨域的时候,我们首先要搞清楚什么是浏览器的同源策略。

同源的定义

如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源,

我以下面的表举例说明

同源策略的说明

那对于跨域的问题,我们怎们去解决呢,在之前两个不同域请求接口的时候,xmlhttprequest因为同源策略的限制导致不能跨域请求。于是利用script标签可跨域,并能够执行返回js脚本的功能,我们利用这个来解决跨域请求的问题,也就是jsonp,但是这种操作有个问题

  • 只支持GET请求
  • 不能获取http的各种状态
  • 安全问题,如果站点不可信,XSS攻击是家常便饭
  • 没有超时机制

那怎么办,W3C组织提出CORS规范(Cross-Origin Resource Sharing )从2004年提出到现在的版本规范时间是2014年。于是现在基本上ajaxfetch 都支持cors

浏览器的支持情况如下

跨域资源共享支持的情况

在跨域请求的时候,我们在响应头中添加Access-Control-Allow-Origin 字段,Access-Control-Allow-Origin 一般设置允许的请求源,如Access-Control-Allow-Origin:www.baidu.com 或设置成 Access-Control-Allow-Origin :* 支持所有站点请求。

设置为* 的acess-control-allow-origin

但是设置了access-control-allow-origin 为* 就行了吗,有时候我们只允许post 方法跨域,有时候我们在使用xmlhttprequestfetch 的时候,我们需要添加一些字段,但是服务那么允许我们添加哪些头,我们是不知道的。

这样跨域请求分为两种,一直是简单跨域请求(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

如果请求没有满足上述条件,那么会进行预检请求。

预检请求的过程大致如下

预请求过程

 

跨域资源共享机制就大致说到这里,我们然后回到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()方法有跨域的限制

如下图所示

access-control-allow-origin 没有设置

这个时候我们设置了 img.crossOrigin=”” (””等价于Anonymous)也是不行。那么我们找一张支持跨域的图片。

支持跨域请求的图片

这个时候没有报错,我们可以总结如下:

在canvas中的getImageData()toBlob() ,toDataURL()方法 对跨域图片操作是除了需要将crossOrigin 属性值设置为anonymous 之外,并且跨域的图片的的响应头字段acess-control-allow-origin 需要进行设置,这样才能在大部分浏览器中不会报跨域错误。

但是在safari浏览器中,并不支持,会报安全问题。

Safari浏览器设置了croosOrigin 但是还是不行

Safari报 SecurityError: The operation is insecure. 那么有没有其他的解决方式。我们通过xmlhttpRequestURL.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>

利用ajax和URL.createObjectURL()解决safari的问题

safari这个问题可以通过上述方案解决,但是也要必须保证图片所在服务设置了acess-control-allow-origin:* 。

参考文档:

Cross-Origin Resource Sharing

HTTP访问控制

 

 

发表评论