注:[n]标识为遗留问题,在文章末尾遗留问题部分有详细解释说明。javascript
以前作了一个在线给图片添加文本框的工具,大致思路是先把图片加载到一个 DOM 结构中,而后经过 html2canvas 导出到一个canvas,最后经过 canvas 自带的 toDataURL
方法导出成图片。css
这个思路并不复杂,可是中间遇到几个小问题:html
跨域图片的导出问题:你能够把图片绘制到 canvas 中,可是不能作任何有关导出数据的操做(好比 toDataURL
),由于 canvas 认为它本身是被污染(tainted)的。(固然本地上传的图片是不存在这个问题的)html5
This protects users from having private data exposed by using images to pull information from remote web sites without permission.java
——出自 canvas-todataurl-securityerrorweb
大概意思是说,这样能够保护用户隐私数据不被暴露。canvas
在 retina 屏幕上canvas 的内容显示变模糊。跨域
图片模糊就算了,为何fillText
输入的文字也会模糊?并且导出来会清晰一点(可是仍是模糊)浏览器
解决过程:安全
第一个问题其实就是解决咱们熟悉的跨域问题。这个工具的主要使用场景是在海外的 i8n 项目,图片通常放在海外的图片服务器上。我给图片添加 crossorigin:anonymous
不生效,因此决定换条路。
既然传统的跨域用法是失败的,可是咱们知道 <img>
的 src
属性能够用 base64编码后的数据表示图片的内容,这样不会存在跨域问题。因此我想用 FileReader
转换图片格式。可是后来才发现 FileReader
一样不容许处理跨域资源…计划泡汤。
而后发现这么个工具CORS Anywhere,是给你的请求头部加 CORS header 的。这样一来应该能够解决跨域问题。(未具体尝试)
这个问题才是今天想讲的主题。
先把网上的解决方法贴出来:
devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = context.webkitBackingStorePixelRatio || 1,
ratio = devicePixelRatio / backingStoreRatio;
var w = $("#code").width();
var h = $("#code").height();
//要将 canvas 的宽高设置成容器宽高的 2 倍
var canvas = document.createElement("canvas");
canvas.width = w * ratio;
canvas.height = h * ratio;
canvas.style.width = w + "px";
canvas.style.height = h + "px";
var context = canvas.getContext("2d");
//而后将画布缩放,将图像放大两倍画到画布上
context.scale(ratio,ratio);
复制代码
上面的代码咱们分两部分看,先忽略上面定义 ratio 值的部分,往下看。 说明一下,canvas 的属性 width/height
和样式表里指定的宽高不一样,前者肯定了这个画布的内容大小,然后者只是显示上的大小。因此上面代码就不难理解了,实际上是把画布的内容高宽放大二倍,而样式上不变,视觉上就会变得精细不少,和二倍图的原理基本上是相似的。
道理我都懂,可是代码开头那一大堆在算什么?
按照上面的逻辑来讲,咱们只须要经过 devicePixelRatio
判断设备是否是 retina 屏幕(不严格地说)就能够了。为何要算他和backingStoreRatio
的比值,这又是个什么东西?
咱们在往 canvas 里画任何东西的时候,实际上浏览器都在把这些写到了一个后备存储空间里。浏览器在从新绘制到屏幕时候,数据就是来自这里。webkitBackingStorePixelRatio
这个值告诉咱们的是后备空间相对 canvas 自己容量的大小。
如今咱们知道了这个值的做用,它是如何控制展现的?
上图展现的是 dpr:bk === 1
的状况,就像没有出现 retina 屏幕这件事同样,导出和汇入两不相干。 关键是二者值都为2的时候也是如此。因此即便是在 retina 屏幕上,也有可能不作多余的代码处理图片也能够很清楚。这也是为何咱们说计算 ratio 的值时咱们要算两者的比值而不是单纯用 dpr。 并且这两个更多时候确实没有任何关系,并非 dpr 为2 bk 的值就也必定高。
dpr:bk === 2
问题出现了。咱们原样把图片放进来,canvas 由于 bk 值为1因此没有对图片作其余处理,再展现到页面上的时候就会模糊。这其实跟通常的图片在 retina 屏幕上模糊的缘由相同。 好比咱们有一个长宽都为30px的图,放到 retina 屏幕上占有 30 csspx 的宽度,可是实际上填充他宽度的有60个物理像素。咱们的图片只提供了30个已知的像素值,其他的30个只能靠浏览器根据周围的像素点去计算。因此会模糊。
下面来讨论为何文字模糊的问题。 刚开始看到文字模糊的时候以为没什么难理解的,明显是和图片一个套路。可是细想以为不对,图片是由于在 dpr 为2的状况下,图片内容宽和图片样式宽倒是相等的因此模糊。可是文字在我打到页面上到画到 canvas 的过程当中,实际像素数是足够的,为何会模糊?
在查了部分资料以后发现,在页面上字体的展现和在 Canvas 里 用fillText 去绘制文字是不同的,后者实际上是在 canvas 里「画」字,而这个画的结果的展现单元和上面图片是同样的,到如今为止咱们能够把这个过程和图片展现想成相同的了。
至于为何下载后会清楚一些可是却不「那么清楚」,咱们当作两个问题来解答。 为何会清楚一些?由于模糊其实是浏览器渲染时候的行为,下载以后查看图片是没有这个像素估算的过程的。 为何却不那么清楚?详细的我不想讲了,具体的能够看这个回答
遗留问题: [1]: 发送的 file 协议的请求到服务器端判断跨域的时候和 http 是同样的标准吗?我我的以为其实应该是的,由于同源策略自己的目的就是出于安全,这一点和你客户端的协议实际上是不要紧的。
参考文章: