Day15 - 中场休息时间 - 来看看htmlToCanvas的实作吧 - 成为Canvas Ninja ~ 理解2D渲染的精髓

经过了连续5篇复杂度略高的物理模拟系列,我在想看官们多少会有点疲乏~

所以我在规划了几篇『中场休息』系列科普文,用来穿插在主要的chapter之间,

休息是为了走更长远的路,还有看更多的物理模拟(X

主要内容会讲一些比较容易理解~一篇之内就可以讲完的案例。

这篇文是『中场休息』系列的第一篇文,我们这次会讲讲怎麽样在Canvas上实作 html 转图像的功能。

htmlToCanvas实作

大部分人提到htmlToCanvas的实作,应该会直接想到html2Canvas这个着名的NPM包,但是我们秉持着NINJA精神当然不能光会用别人写的包,所以我们就来看看这个案例的实作原理

其实htmlToCanvas的实作在这一篇MDN的旧版文章(已经被封存)里面有提到过做法

这边直接把源码贴上来:

<canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>
var canvas = document.getElementById('canvas');
var ctx    = canvas.getContext('2d');

var data   = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
               '<foreignObject width="100%" height="100%">' +
                 '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
                   '<em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span>' +
                 '</div>' +
               '</foreignObject>' +
             '</svg>';

var DOMURL = window.URL || window.webkitURL || window;// 这是一个防呆,因为不同浏览器的createObjectURL方法可能存在於不同对象底下。

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

img.onload = function () {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);//这个api是用来销毁已经用不到的URL,避免记忆体消耗
}

img.src = url;

codepen连结: https://codepen.io/mizok_contest/pen/RwgdpgR

在上面这个案例我们可以看到,html to canvas的实作流程大致如下:

  1. 把dom element用 svg 的 foreignObject tag 塞到svg内部
  2. Blob类的建构式去把svg字串转换成Blob物件
  3. URL.createObjectURL(Blob) 去取得转化出来的Blob物件的URL
  4. 把取得的URL作为src赋予给image,然後在image on load的时候用ctx.drawImage画出来

这边我们就每个步骤稍微说明一下~

为什麽要用foreignObject?

首先我们来讲讲foreignObject 是什麽。

有自己写过svg的同学应该很清楚,svg的原生元素是没有办法做文字段落自动换行的,一般要换行的话,我们只能透过把断行的部分写成多个<tspan>,然後手动指定tspan的座标值,让他看起来像换到下一行

(听起来很笨,但是确实就是这样)

延伸阅读:tspan 在MDN上的介绍页面: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan

foreignObject的存在意义其实就是可以让我们在svg的XML namespace(命名空间)底下去把不同namespace结构语言给渲染出来,像这样透过使用foreignObject,我们就可以轻松地在svg内部实现文字换行~

XML 指的是结构性语言,html/svg都是一种XML,但是他们只能在特定的namespace底下被渲染,不同的namespace底下会有不同的渲染逻辑~

透过使用foreignObjecthtml的内容埋进去svg里面,这样我们就得到了一张具有html外观的svg了~大概可以这样理解。

Blob 是什麽?为什麽要用它?

Blob其实是一种 类档案(File-like)物件

举个例来说~我们常常看到有些网页会有利用<input type="file">档案上传的功能,
这些input在on change时接到的东西其实就是Blob的一种。

而我们这边则是透过手动把svg字串传到Blob类的建构式,来建立一个全新的Blob实例。

new Blob(array, options);

Blob 的第一个参数固定要传一个阵列,而阵列的内容可以允许传入字串(也可以传入其他的东西,可以读MDN上的解释)

延伸阅读: MDN 上关於 Blob()的页面:https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob

之所以用Blob 是因为後面需要用createObjectURL(Blob) 来取得Blob实例的URL。

延伸阅读: MDN 上关於 createObjectURL(Blob)的页面:https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL

如果不想要用Blob来取得image src url,其实也是可以直接把埋入foreignObjectsvg string直接写入<img>src attribute,就像这样:

<img width="600" height="450" src='data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="100%" height="100%"><body xmlns="http://www.w3.org/1999/xhtml"><span style="color:red">123123123</span></body></foreignObject></svg>'>

drawImage部分

其实ctx.drawImage我们之前有提到过(在像素操作概论的篇章)

可以看这边

他就是2DContext所提供的一个用来把图片画到Canvasapi

而这边就是把前面拿到的Blob url 去赋予给 Image.src,然後再把这张图片给画出来~

小结

到这边为止我们大概可以理解htmlToCanvas的实作,但是以实际场景来讲其实很多时候不会像我们上面给的范例一样这麽简单。
打个比方,例如典型的跨域问题,导致我们在程序中无法顺利取得正确的图片/样式表,除此之外,因为为了要防堵资安漏洞,利用foreignObject 去取得html的渲染画面这一操作其实有很多限制,例如:

  • 只能使用内置样式
  • 不能在foreignObject中引入js文件,这意味着有些透过js生成的样式变成需要手动赋予到foreignObjecthtml元素上
  • ...,etc.

而为了应对各种复杂的截图需求,才会有了html2Canvas这样的插件,这个插件实际上是用了很多比较tricky的方法去绕过防堵机制来达成部分因为上述限制而难以实行的问题,但是也因为这样这个插件当然也就会有被认定为bug的状况。

到这边为止是这次的html2Canvas实作介绍~希望大家喜欢 :D


<<:  DAY15 - 处理/读取档案不可或缺的FileReader

>>:  [Day 15]呐呐,还有一半别想跑(後端篇)

二、教你怎麽看source code,找到核心程序码 ep.22:Deeplab的model 部署

文章说明 文章分段 文章说明 deeplab的简单介绍、於我的意义 ep.1 tensorflow的...

[Day 27] 微探讨 Pure pipe 与 Impure pipe

今天要介绍的 Tip 是有关於 pipe 的 pure 与 impure,当没有任何额外的设定下,自...

OpenStack Neutron 介绍 — Linux Bridge Provider Networks

本系列文章同步发布於笔者网站 上篇介绍了 Neutron 的架构,再接下来几天会介绍 Neutron...

Day 11 wireframe 黑白线稿设计 ( topbar + 地图)

今天在男友家写这篇,我一边写他一边趴在我背上乱摸,烦死了快写不完,给我走开RRRRRRRR 因为很多...