整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

将JavaScript代码转换为漂亮的SVG流程图-js2flowchart

js2flowchart 是一个可视化库,可将任何JavaScript代码转换为漂亮的SVG流程图。你可以轻松地利用它学习其他代码、设计你的代码、重构代码、解释代码。这样一个强大的神器,真的值得你拥有,看下面截图就知道了,有没有很强大。

Github

https://github.com/Bogdan-Lyashenko/js-code-to-svg-flowchart

安装使用

  • 安装
yarn add js2flowchart
  • 使用

index.html

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>fly测试</title>
</head>
<body>
 <div style="width:50%;float:left">
 <p id="svgImage"></p>
 </div>
 <div style="width:50%;float:left">
 <textarea name="" id="jsContent" style="width: 90%;height:900px" oninput="createSVG()">
 </textarea>
 </div>
 <script src="./node_modules/js2flowchart/dist/js2flowchart.js"></script>
 <script src="./index.js"></script>
</body>
</html>

index.js

createSVG = () => {
 document.getElementById("svgImage").innerHTML = null;
 let code = document.getElementById("jsContent").value;
 const { createFlowTreeBuilder, createSVGRender } = js2flowchart;
 const flowTreeBuilder = createFlowTreeBuilder(),
 svgRender = createSVGRender();
 const flowTree = flowTreeBuilder.build(code),
 shapesTree = svgRender.buildShapesTree(flowTree);
 const svg = shapesTree.print();
 document.getElementById("svgImage").innerHTML = svg;
};
createSVG();

我们直接在文本域中输入自己的代码,如下,左边会直接生成流程图,这只是一个简单的示例:

js2flowchart的特性以及适用场景(来自官网翻译)

js2flowchart获取您的JS代码并返回SVG流程图,适用于客户端/服务器,支持ES6。

主要特点:

  • 定义抽象级别以仅渲染导入/导出,类/函数名称,函数依赖性以逐步学习/解释代码。
  • 自定义抽象级别支持创建自己的抽象级别
  • 表示生成器,以生成不同抽象级别的SVG列表
  • 定义流树修改器以映射众所周知的API,例如[] .map,[]。forEach,[] .filter到方案上的循环结构等。
  • 销毁修饰符,用于在方案上用一个形状替换代码块
  • 自定义流树修改器支持创建自己的流修改器
  • 流树忽略过滤器完全省略一些代码节点,如日志行
  • 聚焦节点或整个代码逻辑分支突出显示方案的重要部分
  • 模糊节点或整个代码逻辑分支以隐藏不太重要的东西
  • 定义的样式主题支持选择您喜欢的样式
  • 自定义主题支持创建自己的主题,更好地适合您的上下文颜色
  • 自定义颜色和样式支持提供方便的API来更改特定样式而无需样板

用例场景:

  • 通过流程图解释/记录您的代码
  • 通过视觉理解学习其他代码
  • 为有效JS语法简单描述的任何进程创建流程图

以上所有功能可以直接到github上详细了解,用法太多,这里就不在介绍了!

vscode扩展

这么强大的东西,有人肯定说如果在开发的时候实时看到流程图有助于理解代码,官网提供了插件(我在最新版中测试失效了,不知道是否是我使用的有问题还是插件本身的问题),如果感兴趣的可以到扩展商店搜索code-flowchart。如果测试成功,欢迎到评论区分享。以下是我vscode版本和官网的插件使用截图。

如果利用好这个插件,可以开发出Chrome插件,以及其他JavaScript编辑器或者IDEA的插件,由于官方github已经几个月没更新了,所以还不知道未来会不会支持!

总结

js2flowchart是一个比较实用的Javascript插件,可以用来做很多事情,不管是自己写代码。还是阅读别人的代码,都无疑是一大助力,能够帮助我们提升我们的代码能力,更容易的阅读代码,这样学习起来就快了,希望对你有所帮助!如果有什么好的建议,也可以到评论区分享!

者开源了一个Web思维导图,在做导出为图片的功能时走了挺多弯路,所以通过本文来记录一下。

思维导图的节点和连线都是通过 svg渲染的,作为一个纯 js 库,我们不考虑通过后端来实现,所以只能思考如何通过纯前端的方式来实现将svg或html转换为图片。

使用img标签结合canvas导出

我们都知道 img 标签可以显示 svg,然后 canvas 又可以渲染 img,那么是不是只要将svg渲染到img标签里,再通过canvas导出为图片就可以呢,答案是肯定的。

const svgToPng = async (svgStr) => {
    // 转换成blob数据
    let blob = new Blob([svgStr], {
      type: 'image/svg+xml'
    })
    // 转换成data:url数据
    let svgUrl = await blobToUrl(blob)
    // 绘制到canvas上
    let imgData = await drawToCanvas(svgUrl)
    // 下载
    downloadFile(imgData, '图片.png')
}

svgStr是要导出的svg字符串,比如:

然后通过Blob构造函数创建一个类型为image/svg+xml的blob数据,接下来将blob数据转换成data:URL:

const blobToUrl = (blob) => {
    return new Promise((resolve, reject) => {
        let reader = new FileReader()
        reader.onload = evt => {
            resolve(evt.target.result)
        }
        reader.onerror = err => {
            reject(err)
        }
        reader.readAsDataURL(blob)
    })
}

其实就是base64格式的字符串。

接下来就可以通过img来加载,并渲染到canvas里进行导出:

const drawToCanvas = (svgUrl) => {
    return new Promise((resolve, reject) => {
      const img = new Image()
      // 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
      img.setAttribute('crossOrigin', 'anonymous')
      img.onload = async () => {
        try {
          let canvas = document.createElement('canvas')
          canvas.width = img.width
          canvas.height = img.height
          let ctx = canvas.getContext('2d')
          ctx.drawImage(img, 0, 0, img.width, img.height)
          resolve(canvas.toDataURL())
        } catch (error) {
          reject(error)
        }
      }
      img.onerror = e => {
        reject(e)
      }
      img.src = svgUrl
    })
}

canvas.toDataURL()方法返回的也是一个base64格式的data:URL字符串:

最后就可以通过a标签来下载:

const downloadFile = (file, fileName) => {
  let a = document.createElement('a')
  a.href = file
  a.download = fileName
  a.click()
}

实现很简单,效果也不错,不过这样就没问题了吗,接下来我们插入两张图片试试。

处理存在图片的情况

第一张图片是使用base64的data:URL方式插入的,第二张图片是使用普通url插入的:

导出结果如下:

可以看到,第一张图片没有问题,第二张图片裂开了,可能你觉得同源策略的问题,但实际上换成同源的图片,同样也是裂开的,解决方法很简单,遍历svg节点树,将图片都转换成data:URL的形式即可:

// 操作svg使用了@svgdotjs/svg.js库
const transfromImg = (svgNode) => {
    let imageList = svgNode.find('image')
    let task = imageList.map(async item => {
      // 获取图片url
      let imgUlr = item.attr('href') || item.attr('xlink:href')
      // 已经是data:URL形式不用转换
      if (/^data:/.test(imgUlr)) {
        return
      }
      // 转换并替换图片url
      let imgData = await drawToCanvas(imgUlr)
      item.attr('href', imgData)
    })
    await Promise.all(task)
    return svgNode.svg()// 返回svg html字符串
}

这里使用了前面的drawToCanvas方法来将图片转换成data:URL,这样导出就正常了:

到这里,将纯 svg 转换为图片就基本没啥问题了。

处理存在foreignObject标签的情况

svg提供了一个foreignObject标签,可以插入html节点,实际上,笔者就是使用它来实现节点的富文本编辑效果的:

接下来使用前面的方式来导出,结果如下:

明明显示没有问题,导出时foreignObject内容却发生了偏移,这是为啥呢,其实是因为默认样式的问题,页面全局清除了margin和padding,以及将box-sizing设置成了border-box:

那么当svg存在于文档树中时是没有问题的,但是导出时使用的是svg字符串,是脱离于文档的,所以没有这个样式覆盖,那么显示自然会出现问题,知道了原因,解决方法有两种,一是遍历所有嵌入的html节点,手动添加内联样式,注意一定要给所有的html节点都添加,只给svg、foreignObject或最外层的html节点添加都是不行的;第二种是直接在foreignObject标签里添加一个style标签,通过style标签来加上样式,并且只要给其中一个foreignObject标签添加就可以了,两种方式看你喜欢哪种,笔者使用的是第二种:

const transformForeignObject = (svgNode) => {
    let foreignObjectList = svgNode.find('foreignObject')
    if (foreignObjectList.length > 0) {
        foreignObjectList[0].add(SVG(`<style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        </style>`))
    }
    return svgNode.svg()
}

导出结果如下:

可以看到,一切正常。

关于兼容性的问题,笔者测试了最新的chrome、firefox、opera、safari、360急速浏览器,运行都是正常的。

踩坑记录

前面介绍的是笔者目前采用的方案,看着实现其实非常简单,但是过程漫长且坎坷,接下来,开始我的表演。

foreignObject标签内容在firefox浏览器上无法显示

对于svg的操作笔者使用的是svg.js库,创建富文本节点的核心代码大致如下:

import { SVG, ForeignObject } from '@svgdotjs/svg.js'

let html = `<div>节点文本</div>`
let foreignObject = new ForeignObject()
foreignObject.add(SVG(html))
g.add(foreignObject)

SVG方法是用来将一段html字符串转换为dom节点的。

在chrome浏览器和opera浏览器上渲染非常正常,但是在firefox浏览器上foreignObject标签的内容完全渲染不出来:

检查元素也看不出有任何问题,并且神奇的是只要在控制台元素里编辑一下嵌入的html内容,它就可以显示了,百度搜索了一圈,也没找到解决方法,然后因为firefox浏览器占有率并不高,于是这个问题就搁浅了。

使用img结合canvas导出图片里foreignObject标签内容为空

chrome浏览器虽然渲染是正常的:

但是使用前面的方式导出时foreignObject标签内容却是跟在firefox浏览器里显示一样是空的:

firefox能忍这个不能忍,于是尝试使用一些将html转换为图片的库。

使用html2canvas、dom-to-image等库

使用html2canvas:

import html2canvas from 'html2canvas'

const useHtml2canvas = async (svgNode) => {
    let el = document.createElement('div')
    el.style.position = 'absolute'
    el.style.left = '-9999999px'
    el.appendChild(svgNode)
    document.body.appendChild(el)// html2canvas转换需要被转换的节点在文档中
    let canvas = await html2canvas(el, {
        backgroundColor: null
    })
    mdocument.body.removeChild(el)
    return canvas.toDataURL()
}

html2canvas可以成功导出,但是存在一个问题,就是foreignObject标签里的文本样式会丢失:

这应该是html2canvas的一个bug,不过看它这issues数量和提交记录:

指望html2canvas改是不现实的,于是又尝试使用dom-to-image:

import domtoimage from 'dom-to-image'

const dataUrl = domtoimage.toPng(el)

发现dom-to-image更不行,导出完全是空白的:

并且它上一次更新时间已经是五六年前,所以没办法,只能回头使用html2canvas。

后来有人建议使用dom-to-image-more,粗略看了一下,它是在dom-to-image库的基础上修改的,尝试了一下,发现确实可以,于是就改为使用这个库,然后又有人反馈在一些浏览器上导出节点内容是空的,包括firefox、360,甚至chrome之前的版本都不行,笔者只能感叹,太难了,然后又有人建议使用上一个大版本,可以解决在firefox上的导出问题,但是笔者试了一下,在其他一些浏览器上依旧存在问题,于是又在考虑要不要换回html2canvas,虽然它存在一定问题,但至少不是完全空的。

解决foreignObject标签内容在firefox浏览器上无法显示的问题

用的人多了,这个问题又有人提了出来,于是笔者又尝试看看能不能解决,之前一直认为是firefox浏览器的问题,毕竟在chrome和opera上都是正常的,这一次就想会不会是svgjs库的问题,于是就去搜它的issue,没想到,还真的搜出来了issue,大意就是因为通过SVG方法转换的dom节点是在svg的命名空间下,也就是使用document.createElementNS方法创建的,导致部分浏览器渲染不出来,归根结底,这还是不同浏览器对于规范的不同实现导致的:

你说chrome很强吧,确实,但是无形中它阻止了问题的暴露。

知道了原因,那么修改也很简单了,只要将SVG方法第二个参数设为true即可,或者自己来创建节点也可以:

foreignObject.add(document.createElemnt('div'))

果然,在firefox浏览器上正常渲染了。

解决img结合canvas导出图片为空的问题

解决了在firefox浏览器上foreignObject标签为空的问题后,自然会怀疑之前使用img结合canvas导出图片时foreignObject标签为空会不会也是因为这个问题,同时了解了一下dom-to-image库的实现原理,发现它也是通过将dom节点添加到svg的foreignObject标签中实现将html转换成图片的,那么就很搞笑了,我本身要转换的内容就是一个嵌入了foreignObject标签的svg,使用dom-to-image转换,它会再次把传给它的svg添加到一个foreignObject标签中,这不是套娃吗,既然dom-to-image-more能通过foreignObject标签成功导出,那么不用它必然也可以,到这里基本确信之前不行就是因为命名空间的问题。

果然,在去掉了dom-to-image-more库后,重新使用之前的方式成功导出了,并且在firefox、chrome、opera、360等浏览器中都不存在问题,兼容性反而比dom-to-image-more库好。

总结

虽然笔者的实现很简单,但是dom-to-image-more这个库实际上有一千多行代码,那么它到底多做了些什么呢,点个关注,我们下一篇文章再见。

着 Web 技术的发展,有很多需要将 HTML 内容转换为 PDF 文档并下载的场景,比如常见的收据、发票、电子报告、对账清单、文档翻译等等。

本文将研究当前比较流行的基于 Node.js 技术栈的 HTML 转 PDF 库:Puppeteer、jsPDF 和 PDFKit,大纲如下:

  • Puppeteer 及优缺点
  • jsPDF 及优缺点
  • PDFKit 及优缺点
  • Puppeteer vs jsPDF vs PDFKit 功能对比

1.Puppeteer

1.1.使用 Puppeteer 生成 PDF

Puppeteer 是谷歌开发的一个 Node.js 库,为控制无头(或完整)Chrome 或 Chromium 浏览器提供了一个高级 API。它是最流行的开源 HTML 到 PDF 转换器,支持 HTML、CSS 和 JavaScript。

Puppeteer 允许您自动化Web浏览器中的各种任务,例如 Web 抓取、网站测试、截图创建和 PDF 生成。它利用 Chrome 或 Chromium Web 浏览器的功能将 HTML 内容呈现为 PDF 文件。

下面使用 Puppeteer 快速初始化一个项目:

nvm use 20.6.1

mkdir puppeteer-demo

cd puppeteer-demo

npm init -y

npm install puppeteer

touch index.js

index.js 中添加如下代码:

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new',
  });

  const page = await browser.newPage();

  await page.setContent(
    `<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <style>
        body {
          display: grid;
          place-items: center;
          margin: 0;
          background-color: #ccc;
          min-height: 100vh;
        }
      </style>
      <h1>Hello, Puppeteer!</h1>
    </body>
    </html>`
  );

  await page.pdf({ path: 'result.pdf', format: 'A4' });

  await browser.close();
})();

在上面的代码中,我们导入 Puppeteer 库,启动无头 Chromium 浏览器,并在浏览器中创建一个新页面。我们还会根据页面内容和指定选项(如路径和格式)生成 PDF,生成完成后关闭浏览器并释放资源。

1.2.优点

  • 您可以完全控制要在 PDF 中包含网页的哪些部分。您可以指定具体的元素、部分或整个页面
  • Puppeteer 提供了自定义选项,如指定页面大小、边距、页眉和页脚,使您可以根据特定需求定制 PDF 布局
  • Puppeteer 可以捕捉 PDF 中的交互元素,如超链接和表单字段,适用于生成交互式 PDF 文件。

1.3.缺点

  • 使用 Puppeteer 生成的 PDF 有时可能比使用类似库生成的PDF要大。这会影响下载时间和存储成本。
  • 使用 Puppeteer 渲染复杂或大型网页可能会消耗资源,导致PDF生成变慢和内存使用增加。

2.PDFKit

PDFKit 是 JavaScript 生态系统中最古老和最成熟的 PDF库 之一。目前仍得到良好维护并定期更新。该库通常使用 Node.js 在服务器端环境中创建和操作 PDF 文档。它允许您通过定义每个页面的内容、布局和格式来以程序方式生成PDF文件。

PDFKit 提供了用于 PDF 文档生成的高级 API,并支持各种功能,使其成为创建自定义 PDF 的多功能工具。作为 PDFKit 的包装器,已开发出多个 PDF 库,支持自定义字体和图像嵌入。

2.1.使用 PDFKit 生成 PDF

让我们来快速初始化一个 PDFKit 项目:

nvm use 20.6.1

mkdir pdfkit-demo

cd pdfkit-demo

npm init -y

npm install pdfkit

touch index.js

index.js 中添加如下代码:

import fs from 'node:fs';
import PDFDocument from 'pdfkit';

const doc = new PDFDocument();

const stream = fs.createWriteStream('example.pdf');

doc.pipe(stream);
doc.fontSize(12).text('Hello, PDFKit!', { align: 'center' });
doc.end();

stream.on('finish', () => {
  console.log('success');
});

执行 node index.js 后,可以看到 PDF 可以生成生成,并触发 finish 事件的回调函数执行,打印出了 success

2.2.优点

  • 由于它是开源的,并由社区积极维护,PDFkit 可以不断改进和更新
  • PDFKit 可对 PDF 文档的内容、布局和格式进行精细控制。您可以根据自己的特定需求创建高度定制的 PDF 文档
  • PDFKit 是根据用户请求、数据或其他服务器端进程动态生成 PDF 的绝佳选择

2.3.缺点

  • 创建具有高级布局和功能的复杂 PDF 可能具有挑战性,可能需要充分了解库的 API
  • 对于新手来说,PDFKit 的学习曲线可能比更直接的 HTML 到 PDF 转换工具更陡峭,因为它需要对文档结构进行更多的手动控制

3.jsPDF

jsPDF 是一个流行的 JavaScript PDF 生成器,允许用户动态在 Web 浏览器中生成 PDF 文件。这个库维护良好,稳定易用,并且有丰富的文档。

jsPDF 在客户端操作,非常适合在 Web 应用程序中生成 PDF ,无需在服务器端生成。它可以修改现有布局,并允许用户通过自定义来控制他们的 PDF。还可以用来编辑现有的 PDF 文档或从头创建一个,包括图像、表格和形状等内容。

3.1.使用 jsPDF 生成 PDF

示例中使用官方提供的 CDN 地址 https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
  </head>
  <body>
    <style>
      body {
        display: grid;
        place-items: center;
        margin: 0;
        background-color: #ccc;
        min-height: 100vh;
      }
    </style>
    <h1>Hello, jsPDF!</h1>
    <script>
      document.addEventListener("DOMContentLoaded", function () {
        const pdf = new window.jsPDF();
        const element = document.body;

        pdf.html(element, {
          callback: function (pdf) {
            // Save the PDF to a file or display it
            pdf.save("output.pdf");
          },
        });
      });
    </script>
  </body>
</html>

文档地址:http://raw.githack.com/MrRio/jsPDF/master/docs/index.html

3.2.优点

  • jsPDF 完全在客户端(页面)上运行,这使它成为在 Web 应用程序中生成 PDF 的一个很好的选择,而无需外部服务
  • jsPDF 相对易于使用,尤其是对于基本的 PDF 生成任务。您只需几行 JavaScript 代码即可创建 PDF 文档
  • 由于 jsPDF 是一个 JavaScript 库,它可以与 Web 应用程序无缝集成,并可以与其他 JavaScript 库和框架一起使用

3.3.缺点

  • 在客户端生成 PDF 可能会耗费大量资源,尤其是大型或复杂文档。这会影响浏览器性能或导致内存消耗问题
  • jsPDF 在现代 Web 浏览器中得到广泛支持,但在旧的浏览器或具有较少 JavaScript 支持的环境中可能无法按预期工作
  • 与 PDFKit 等服务器端 PDF 生成库不同,jsPDF 具有有限的高级功能和能力。它可能不适用于复杂的PDF要求。

4.Puppeteer、jsPDF 和 PDFKit 功能比较

为便于大家参考,下面总结了 Puppeteer、jsPDF 和 PDFKit 库在 PDF 生成、文件大小、浏览器集成和社区支持方面的比较:

5.结论

当为 HTML 转 PDF 需求选择实现方案时,可以参考以上实现方案。如果你想从头开始生成 PDF,PDFKit 可能是你最好的选择。要通过 HTML 或者 SVG 内容快速转换为 PDF,jsPDF 可能更合适。对应网页渲染和交互式内容,强烈建议你选择 Puppeteer。

最后,方案选择在很大程度上还取决于你的项目类型、规范及开发者个人喜好。


如果文章对你有帮助,欢迎关注和转发,感谢你的支持!