网页截图方案总结
最近在做一个项目,写好样式后,发现在微信端需要保存图片,为了方便(偷懒),第一个想到用什么办法把dom节点直接变成图片。在知识的海洋一番畅游,发现了几种方案,网上现有的文章都不是很齐全,所以在这里汇总一下。
概述
网页截图可以在后端进行,也可以在前端进行,后端比较常用的是用无头浏览器phantomjs配合实现截图。后端截图适用于一次截图保存,多次使用的情况。如果截图次数较多,请求比较频繁的话,对服务器的压力还是比较大的,这种情况,最好的方式就是在前端实现截图,本文主要介绍前端截图方案。
1 原理
原理1:把内容绘制到canvas上,然后通过toDataURL、toBlob等得到图像资源,再根据情况转换为不同的图片格式。
原理2:使用svg,通过createObjectURL或encodeURIComponent处理svg得到图像资源,可以把svg绘制到canvas。
原理3:使用免费或付费现成API,pdfcrowd、web2pdfconvert、url2png等。
2 方案
方案1:将内容直接绘制到canvas。
方案2:遍历dom节点绘制到canvas。
方案3:将dom节点处理后嵌入到svg的foreignObject中。
2.1 各种方案优缺点对比
方案1(直接绘制) | 方案2(遍历dom) | 方案3(嵌入svg) | |
---|---|---|---|
优点 | 可完全还原设计稿 | 简单快捷,方便复用 | 更简单快捷 |
缺点 | 不能复用,代码繁琐 | 需要考虑各种复杂的css样式搭配,各种特殊标签,精确度问题等,可能出现部分内容丢失等情况 | svg中不允许外部资源(js,css,img的url等),svg中不支持执行js,需要经过处理,也不能完全还原 |
2.2 根据以上方案,比较成熟的解决方式有
方案1(直接绘制):原生canvas的api(代码复杂,繁琐)、konva.js(面向对象方式,比原生好用一点)
方案2(遍历dom):html2canvas.js(导出图片格式需要搭配canvas2image.js,虽然官方文档说处于试验环境,还有重大改版,不推荐正式使用,但是14年就被Twitter等用于生产环境)、dom-to-image.js(自带导出图片格式,但是很久没有维护了)
方案3(嵌入svg):rasterizeHTML.js(可兼容方案2,也有很多限制详见Limitations)
3 常见问题
3.1 图片跨域问题
js异步获取图片在不同源的情况下会存在跨域问题。
① 服务器设置cors,前端设置img的属性crossOrigin='anonymous'。
② 如果图片位于第三方,不能设置cors的话,可以通过服务器代理把图片代理到当前域下。
注意:因为有些图片服务器设置不同,如果网页其他地方有img标签请求相同的图片,可能会缓存不允许跨域的响应头,在服务器设置是需要注意。如果图片不多的情况可以在js请求的图片url中添加参数以区别img标签,防止缓存影响跨域设置。
3.2 导出图片模糊问题
① 如果按照一比一绘制导出图片,一般都会有点模糊,可以在绘制时将canvas和内容等比例放大绘制,导出时再缩放为原尺寸。
② 建议px为单位,单位如果使用不是px的话,在计算时会出现很多小数,导致模糊问题。
③ 使用img标签代替背景图。
④ canvas的抗锯齿是默认开启的,需要关闭抗锯齿来实现图像的锐化MDN: imageSmoothingEnabled
3.3 由于资源加载不全导致的内容不完整问题
在绘制canvas时,如果部分资源没有加载完成,绘制内容会丢失,除了设置一定的延迟外,还可以通过Promise.all配合onload事件确保资源已经加载完成。
3.4 canvas如何居中绘制
很多时候有居中绘制的需求,可以通过ctx.measureText或者img.width获取宽度,然后x = (总宽度 - 绘制宽度) / 2。
ctx.measureText还可以用来绘制自动换行,超出省略等。
3.5 canvas绘制非正圆圆角
// 绘制圆角并裁剪
const drawRoundedRect = function (ctx, x, y, width, height, r) {
ctx.save();
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + width, y, x + width, y + r, r);
ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
ctx.arcTo(x, y + height, x, y + height - r, r);
ctx.arcTo(x, y, x + r, y, r);
ctx.closePath();
ctx.clip();
}
评论已关闭