实现方式
一.canvas
html2canvas
过程或者原理(如何将dom转换成canvas图片?)
梳理了其大致的思路:
- 递归取出目标模版的所有DOM节点,填充到一个rederList,并附加是否为顶层元素/包含内容的容器 等信息
- 通过z-index ,postion, float等css属性和元素的层级信息将rederList排序,计算出一个canvas的renderQueue
- 遍历renderQueue,将css样式转为setFillStyle可识别的参数,依据nodeType调用相对应canvas方法,如文本则调用fillText,图片drawImage,设置背景色的div调用fillRect等
- 将画好的canvas填充进页面
无论是排序优先级的计算还是从css到canvas的转换,毫无疑问都是些巨麻烦的事,尤其是放在真实的业务场景里,DOM模版中往往会包含复杂的样式与排版,html2canvas 足足用了20多个js来实现这层转换,复杂成度可见一斑。
代码实现过程:
1.在vue项目中安装依赖
npm install html2canvas
2.引入
import html2canvas from “html2canvas”;
3.使用并生成
//生成base64
getImg(cab){
setTimeout(function () {
html2canvas(document.getElementById("superMap"), { //superMap整个页面的节点
backgroundColor: null, //画出来的图片有白色的边框,不要可设置背景为透明色(null)
useCORS: true, //支持图片跨域
scale: 1, //设置放大的倍数
}).then((canvas) => {
//截图用img元素承装,显示在页面的上
let img = new Image();
img.src = canvas.toDataURL("image/jpg"); // toDataURL :图片格式转成 base64
this.imgUrl =img.src
//回传子组件base64
cab(this.imgUrl)
//如果你需要下载截图,可以使用a标签进行下载
// let a = document.createElement("a");
// a.href = canvas.toDataURL("image/jpeg");
// a.download = "test";
// a.click();
});
}, 500);
}
//根据业务需求,将base64赋值给img标签,相关的逻辑要依据情况自行处理
<img width="100%" :src="imgUrl" alt="" />
//上传也是根据业务需求来定
sendUrl () {
// 如果图片需要formData格式,就自己组装一下,主要看后台需要什么参数
const formData = new FormData()
formData.append('base64', this.company.fileUrl)
formData.append('userId', 123)
formData.append('pathName', 'aaa')
this.$ajax({
url: apiPath.common.uploadBase,
method: 'post',
data: formData
}).then(res => {
if (res.code && res.data) {
//to do
}
})
}
优点:
1.稳定性:但自14年起便已经被Twitter 等用在了生产环境,所以虽然有诸多限制,稳定性应该还是保障的。
2.支持异步操作:html2canvas代码中大量使用了Promise
3.灵活性较高,环境依赖上也只需要确保浏览器支持canvas就可以了
缺点:
1.复杂,过程复杂
2.慢,本地运行大约4秒左右。原因自然是因为大量的计算与递归调用,这是无可避免的
3.两个限制:
-
无法跨域跨域资源,现在目前看,是有跨域设置的,有待验证。
解决方案1: 图片服务端设置允许跨域(返回 CORS 头),html2canvas设置useCORS:true,//(图片跨域相关)allowTaint:false,//允许跨域(图片跨域相关);对于微信头像,可通过配置服务端代理转发(forward)实现
解决方案2:
将文件读入到blob文件对象,然后用URL.createObjectURL()方法转换成img src可用的地址,然后再转canvas
getImage:function (url,imgId) { var xhr = new XMLHttpRequest(); xhr.open('get', url, true); xhr.responseType = 'blob'; xhr.onload = function () { if (this.status == 200) { document.getElementById(imgId).src = URL.createObjectURL(this.response); } }; xhr.send(); },
-
无法渲染iframe,flash等内容,但目前支持svg,但是在实践中,也是渲染不了svg格式的图片,需要曲线救国
-
图片传输的数据格式,如果后台无法支持base64,前端要做处理:1.通过new File()将base64转换成file文件,此方式需考虑浏览器兼容问题。2.先将base64转换成blob,再将blob转换成file文件,此方法不存在浏览器不兼容问题。https://blog.csdn.net/yin13037173186/article/details/83302628
二、s v g
首先,svg本来就是矢量图形;其次,svg是可以用xml描述的;再其次,用来描述svg的标签里有个 foreignObject标签,这个标签可以加载其它命名空间的xml(xhtml)文档。也就是说,如果使用svg的话,我们不再需要一点点的遍历,转换节点;不用再计算复杂的元素优先级,只需要一股脑的将要渲染的DOM扔进就好了,剩下的就交给浏览器去渲染。
让我们理一理思路:
- 首先,我们要声明一个基础的svg模版,这个模版需要一些基础的描述信息,最重要的,它要有这对标签
- 将要渲染的DOM模版模版嵌入foreignObject
- 利用Blob构建svg图像
- 取出URL,赋值给
优点:
1.渲染速度快,没有了复杂的计算和递归,速度大大提升
2.使用SVG截图可获取同域< iframe>内容进行渲染
缺点:
1.跨域资源无法加载
2.如lazyload等通过js加载的资源无法加载
3.内联或js操作background-image无法加载
4.引入了外部样式表、图片、而且还可能某些标签不符合xml规范(如缺少闭合标签等)
核心代码:
import Rasterizehtml from "rasterizehtml";
const btn = document.getElementById("save-btn");
btn.addEventListener("click", () => {
// drawURL()加载的URL必须是同域名URL或支持跨域的URL
// 下面的URL是随便写的,记得换成同域名URL或支持跨域的URL
const url = "https://www.baidu.com";
const canvas = document.createElement("canvas");
const opts = {
executeJs: true,
height: screen.height,
width: screen.width
};
Rasterizehtml.drawURL(url, canvas, opts).then(res => {
const base64 = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(res.svg)));
Render(base64, opts.width, opts.height, img => {
document.body.appendChild(img);
Download(base64, "screenshot.png");
});
}, err => alert("截图失败,请重新尝试"));
});
三、火狐浏览器的官方网站drawWindow()
https://zhuanlan.zhihu.com/p/149483404
这个方法和上面提到html2canvas不同之处在于,它不分析页面元素,它只针对区域,也就是说,它接受的参数是四个数字标志的区域,不论这个区域中什么地方,有没有页面内容。这种方法,本人没用过,搬运参考了大佬的方案,以下内容仅供参考。
void drawWindow(
in nsIDOMWindow window,
in float x,
in float y,
in float w,
in float h,
in DOMString bgColor,
in unsigned long flags [optional]
);
这个原生的JavaScript方法看起来非常的完美,但这个方法不能使用在普通网页中,因为火狐官方发现这个方法会引起有安全漏洞,在这个bug修复之前,只有具有“Chrome privileges”的代码才能使用这个
drawWindow()
函数。
虽然有很大的限制,但周折一下还是可以用的,在火狐addon插件中,main.js就是具有“Chrome privileges”的代码。我在网上发现了一段火狐插件SDK里自带代码样例:
var window = require('window/utils').getMostRecentBrowserWindow();
var tab = require('tabs/utils').getActiveTab(window);
var thumbnail = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
thumbnail.mozOpaque = true;
window = tab.linkedBrowser.contentWindow;
thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
var aspectRatio = 0.5625; // 16:9
thumbnail.height = Math.round(thumbnail.width * aspectRatio);
var ctx = thumbnail.getContext("2d");
var snippetWidth = window.innerWidth * .6;
var scale = thumbnail.width / snippetWidth;
ctx.scale(scale, scale);
ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, snippetWidth * aspectRatio, "rgb(255,255,255)");
// thumbnail now represents a thumbnail of the tab
这段代码写的非常清楚,只需要依据它做稍微的修改就能适应自己的需求。