willson-wang / Blog

随笔
https://blog.willson-wang.com/
MIT License
70 stars 10 forks source link

canvas toDataURL 跨域问题 #86

Open willson-wang opened 4 years ago

willson-wang commented 4 years ago

当我们需要借助canvas的toDataURL()、toBlob()、getImageData()方法处理跨域图片时,往往会出现以下几种错误

// 基础代码
const img1 = document.createElement('img')
const img = new Image()

img.src = 'https://ci-yunketest.oss-cn-shenzhen.aliyuncs.com/3Lcx34R_gFBxqgCxAtXP0-8H2a51LtaX2KZLVEA60YQUxT_XwGCr4uCJzUyY7nnP.jpg'

img.onload = function () {
    console.log('xxx load')
    const canvas = document.createElement('canvas')
    const context = canvas.getContext('2d')
    canvas.width = img.width
    canvas.height = img.height

    context.drawImage(img, 0, 0, img.width, img.height)

    const url = canvas.toDataURL('image/png')
    img1.src = url
    document.body.appendChild(img1)
}

img.onerror = function () {
    console.log('xxx error')
}

错误一、画布被污染

Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
    at Image.img.onload (http://127.0.0.1:5500/src/img-cors/index.html:29:36)

出现原因:是由于在 位图中的像素可能来自多种来源,包括从其他主机检索的图像或视频,因此不可避免的会出现安全问题。尽管不通过 CORS 就可以在 中使用其他来源的图片,但是这会污染画布,并且不再认为是安全的画布,这将可能在 检索数据过程中引发异常。所以在"被污染"的画布中调用以下方法将会抛出安全错误toDataURL()、toBlob()、getImageData()方法时会抛出错误,这样做的目的是可以避免未经许可拉取远程网站信息而导致的用户隐私泄露。

解决方法:首先图片url所在的服务器要允许跨域加载,即设置了对应的Access-Control-Allow-Origin响应头;其次需要为img标签添加crossOrigin属性,具体如下所示

// anonymous: 执行一个跨域请求(比如,有 Origin HTTP header)。但是没有发送证书(比如,没有 cookie,没有 X.509 证书,没有 HTTP 基本授权认证)。如果服务器没有把证书给到源站(没有设置 Access-Control-Allow-Origin HTTP 头),图像会被污染,而且它的使用会被限制。use-credentials: 一个有证书的跨域请求(比如,有 Origin HTTP header)被发送 (比如,cookie, 一份证书,或者 HTTP 基本验证信息)。如果服务器没有给源站发送证书(通过 Access-Control-Allow-Credentials HTTP header),图像将会被污染,且它的使用会受限制。

img.crossOrigin = ''; // 会被转成默认值anonymous
img.crossOrigin = 'abc'; // 会被转成默认值anonymous
img.crossOrigin = 'anonymous';
img.crossOrigin = 'use-credentials';

当我们不论是通过img标签还是通过new Image的方式来加载图片,当我们没有主动设置crossorigin属性时,都不使用 CORS 发起请求去获取图片资源;

错误二、图片跨域

Access to image at 'https://ci-yunketest.oss-cn-shenzhen.aliyuncs.com/3Lcx34R_gFBxqgCxAtXP0-8H2a51LtaX2KZLVEA60YQUxT_XwGCr4uCJzUyY7nnP.jpg' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

出现原因:1、图片所在服务器本身没有设置cors;2、图片所在服务器已经设置了cors,我们在代码内先通过img标签请求过图片资源之后,我们又通过js的new Image去请求该图片资源;代码如下所示

<img style="width: 70px; height: 70px" src="https://ci-yunketest.oss-cn-shenzhen.aliyuncs.com/3Lcx34R_gFBxqgCxAtXP0-8H2a51LtaX2KZLVEA60YQUxT_XwGCr4uCJzUyY7nnP.jpg" alt="">

<script>
const img = new Image()
img.src = 'https://ci-yunketest.oss-cn-shenzhen.aliyuncs.com/3Lcx34R_gFBxqgCxAtXP0-8H2a51LtaX2KZLVEA60YQUxT_XwGCr4uCJzUyY7nnP.jpg'

img.crossOrigin = '';
img.onload = function () {
    const canvas = document.createElement('canvas')
    const context = canvas.getContext('2d')

    context.drawImage(img, 0, 0, img.width, img.height)
    const url = canvas.toDataURL('image/png')

}
</script>

我们在img标签上没有设置crossorigin属性,那么img标签去请求图片的时候就不会以cors的方式去请求图片;而我们服务器一般对静态资源都设置了缓存策略;导致当我们在通过js的new Image方式去请求图片时,为了让canvas不脏,所以我们会添加crossOrigin属性;又url一致,这就导致浏览器出现改图片跨域的提示

解决方法:

  1. img标签上添加crossOrigin属性,设置之后注意清下浏览器缓存
<img style="width: 70px; height: 70px" crossorigin src="https://ci-yunketest.oss-cn-shenzhen.aliyuncs.com/3Lcx34R_gFBxqgCxAtXP0-8H2a51LtaX2KZLVEA60YQUxT_XwGCr4uCJzUyY7nnP.jpg" alt="">
  1. 改变new Image时url的路径,比如在url后面加一个时间戳
img.src = 'https://ci-yunketest.oss-cn-shenzhen.aliyuncs.com/3Lcx34R_gFBxqgCxAtXP0-8H2a51LtaX2KZLVEA60YQUxT_XwGCr4uCJzUyY7nnP.jpg?' + new Date().getTime()

当url后面加一个时间戳之后,浏览器不会判断是同一个url,会重新发起一个请求;

推荐方式2解决问题,方式1需求清缓存,方式2不需要

图片不添加与添加crossOrigin属性,请求图片资源的请求头与响应头

不添加

添加

总结:

碰到canvas处理图片的时候跨域只要抓住两个核心点。1、图片资源所在服务器是否已允许跨域;2、canvas是否被污染

参考资料

启用了 CORS 的图片 img:图像嵌入元素