最近在做一个项目,写好样式后,发现在微信端需要保存图片,为了方便(偷懒),第一个想到用什么办法把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();
 }

标签: 前端截图, canvas, svg

评论已关闭