项目代码:https://github.com/imaoda/drawio-embed,欢迎star
前言
基本流程是引入了两款开源的绘图工具 drawio (流程图) 和 kityminder _(思维导图)_,将绘制工具插件式的嵌入到页面中,能在需要时唤起,提供用户编辑,并在编辑完成后将数据流回协同文档。
本文会主要围绕如何在前端「嵌入」_(其他过程不在这里展开)_,主要包括:
提炼通用嵌入逻辑,开源了
drawio-embed
,方便大家在项目里一键嵌入流程图浅析了这些图嵌入协同文档中的原理,包括如何互相的调用,以及数据传递与转换
什么是嵌入
所谓「嵌入」,指的将 drawio
和 kityminder
静态资源部署后,以 iframe
形式载入到协同文档中,并由协同文档调度进行开启/关闭,接受协同文档的指令,并完成 UI 的控制和数据的传递。
协同文档与这些嵌入页,通过事件系统完成跨 javascript
进程的通信,借助 postMessage
和监听 message
完成协议的收发。drawio
本身定义了一套比较基础的、基于 iframe 通信协议,这套协议基本可以满足绝大多数的需求。
使用协同文档的嵌入方案到你的网页中
如果你觉得 协同文档 的流程图/思维导图嵌入方式很酷,那么现在,你只需「几行代码」,让自己的页面也支持流程图。
我们抽离了 协同文档 嵌入方案中可以复用的逻辑,并进一步精简,发布了 drawio-embed
包到 npm
上。一般的场景可以开箱即用,另外也方便和大家一起学习探讨。
在实际嵌入应用中通常需要留意 xss 防范以及 svg 本身的安全问题
快速嵌入
引用 npm 包,或者直接 umd 方式,简单几步,开箱即用
使用 NPM 引入
import drawioEmbed from "drawio-embed";// 初始化const openDrawio = drawioEmbed();// 监听返回的图片数据("drawioImageCreated", evt => {const { imageContent, imageType } = evt;});// 在需要时打开 drawio 开始编辑openDrawio();
使用 UMD 引入
<scriptdefersrc="/drawio-embed/umd/"onload=" = drawioEmbed()">script>
流程图初始化时会占用较多的网络资源,通常可选择滞后/按需加载。第一次打开流程图后,会在浏览器种下 service-worker 的缓存,之后几乎都是秒开。
DIY 一下
黏贴下面代码到本地并用浏览器打开即可,或查看 Demo地址(/drawio-embed/)
<html><body><button onclick="openDrawio()"> 打开编辑 (实际使用时,需判断 drawio 是否初始化好)button><div>png图:div><img id="png" src="" /><div>svg图:<small>(不失真,但需考虑安全性兼容性)small>div><div id="svg">div>body><scriptsrc="/drawio-embed/umd/"onload="openDrawio = drawioEmbed()" >script><script>const pngDom = ("#png");const svgDom = ("#svg");// 监听返回的图片("drawioImageCreated", ({ imageType, imageContent }) => {if (imageType === "png") = imageContent;if (imageType === "svg") = imageContent; } );script>html>
效果如下图:
部署自己的 drawio
默认调用 drawio 官网的流程图,服务器在海外,初次访问较慢 _(不过初次加载后会建立 service worker 缓存)_。当然,有条件的还是可以自己部署一套,部署十分方便,只需 2 步,完成静态资源托管
git clone /jgraph/drawio
静态资源托管
src/main/webapp
路径下的资源
在初始化的时候,更改配置,指向自己部署的 drawio
const openDrawio = drawioEmbed(yourWebsiteUrl);
完全掌控 drawio-embed
从一键嵌入,到完全掌控,也就再多花几分钟,看看 6 类场景的应用
初始化
import drawioEmbed from "drawio-embed";const openDrawio = drawioEmbed();
执行 drawioEmbed()
将在你的页面中,插入一个 iframe,iframe 加载流程图,并挪动到可视区域之外
首次初始化之后,再次重复执行 drawioEmbed()
不会再初始化,但依然会返回 openDrawio
的引用,必要的时候,我们可以借此来获得 openDrawio
的引用
打开流程图编辑器
在页面中,通过初始化时的函数,来唤起 drawio 页面
import drawioEmbed from "drawio-embed";const openDrawio = drawioEmbed();// 在特定的时机,打开流程图 = () => openDrawio();
此时 drawio 的 iframe 会全屏覆盖你的窗口。
打开流程图编辑器,同时加载一个现有的流程图资源
如果需要打开一个已有的 svg 图片,比如继续编辑上次的内容,可以:
传递一个 svg 文本
传递一个网络地址
openDrawio("/1.svg");openDrawio("...");
注:这里的 svg 图片须为通过本项目导出的 svg,而非任意 svg。如果需要使用网络地址,则需要同域,或者允许跨域
尝试打开一个尚未加载好的编辑器
编辑器初始化需要一定的时间,如果过早的调用 openDrawio()
打开,可能实际无法打开,原因有:
流程图的网络资源还在加载
流程图的内部初始化尚未结束
此时会返回 reject
态,可以借此提醒用户
opneDrawio().catch(() => {("编辑器还在初始化中,请稍后再打开...");});
另外,如果需要精细控制,可以:
监听
drawioLoaded
事件,该事件只会触发一次,表明流程图编辑器已经 ready()
判断流程图编辑器是否已经加载好
获取流程图编辑器导出的图片
在 drawio 编辑界面,点击右上角的「保存」按钮,可将编辑的流程图导出,同时会默认自动关闭编辑器窗口
如果不想点保存自动关闭,可在流程图 url 上加上 hold=1 的 query
为
window
绑定监听drawioImageCreated
事件在触发事件时,提取其中的数据
window.addEventListener("drawioImageCreated", evt => {const { imageType, imageContent } = evt;switch (imageType) {case "png":console.log("base64 格式的 png 图片信息", imageContent);break;case "svg":console.log("svg 标签文本", imageContent); // ...break; }});
一次用户保存,会在两个 eventLoop 里派发出两个 drawioImageCreated
事件,通过 imageType
来区分类别
导出 PNG
导出 SVG
开发者可根据实际需求,选择性监听
关闭流程图编辑器
通常,用户在编辑时,如果点击了「保存」或者「取消」按钮,会自动关闭编辑器。当然我们也可以主动控制其关闭
openDrawio.close();
嵌入原理浅析
从嵌入结构、通信协议、状态转换、以及图片信息合成 4 个方面介绍一下。
tips:下文的配图均在 协同文档 流程图里绘制完成
嵌入方案
drawio 流程图在初始化后以 iframe 的形式嵌入,仿佛是一个附属的应用程序,有两个状态:
隐藏:iframe 在视野不可见
显示:iframe 在视野可见,并且绝对定位置于页面顶部
页面与流程图的通信
页面和流程图是父子 frame 的关系,因此采用 postMessage 进行双向通信。postMessage 仅支持字符串,因此复杂的数据结构通过 JSON 来序列化/反序列化,我们在协议里约定了协议名和协议体
基础的通信的协议有:
协议名 | 含义 | 来源方 |
---|---|---|
init | 流程图加载完 | 流程图 |
save | 用户点击了保存键 | 流程图 |
exit | 用户点击了取消键 | 流程图 |
export | 有图片数据导出 | 流程图 |
load | 请求加载图片 | 父页面 |
export | 请求导出图片 | 父页面 |
spinner | 显示/隐藏 loading | 父页面 |
基于协议,可以「桥接」两个进程,虽然能实现通信,但对开发者来说,维护起来代码量会增加不少。 drawio-embed
在这里充当了通信的代理者,让整个流程变得更简化,只需关注结果。除此之外,drawio-embed
还做了一个事情,实现了协议和流程图的生命周期的一致性,因此开发者通常无需根据协议同时去关注数据和 UI 的变更
流程图的生命周期
本环节揭示了流程图创建、显示、隐藏、输入、输出的过程
主要涵盖以下的过程:
父页面初始加载,并隐藏
监听 load 事件自动打开流程图
监听 save/exit 事件自动隐藏流程图
监听 export 事件,合成新的 Event,发送给父页面
维护流程图的开启/关闭状态
巧妙地组装 svg
drawio-embed
最具有特色的地方,在于构建出一个组装的 svg,它的优势在于:
像一个普通的 svg 图片一样被各种预览工具打开,如 chrome、微信 webview
能携带 mxfile 编辑数据,在 drawio 中恢复编辑
由 drawio-embed
导出的 svg 数据能够兼顾展示和恢复编辑的需求,无需开发者自行关联。
对于导出的 svg 数据,只需 openDrawio(svg) 即可打开流程图编辑器,恢复编辑,支持网络地址或本地资源,对于网络地址,会先 fetch 到内容再导入到流程图里
更一般的嵌入方案
drawio 的嵌入,其实给我们一个 iframe 嵌入的通用思路,我们可以沿用此思路,对其他绘制软件进行改造
思维导图的嵌入改造
百度开源的 kityminder
本身是支持导入导出的,我们需要做的是
补齐 iframe 通信协议
关联协议,与思维导图的导入导出 api 挂钩
借助
drawio-embed
桥接思维导图与 协同文档 的通信
多套嵌入时候,通信协议需通过命名空间区分开,比如加一个前缀
更一般的改造
「嵌入模式」的改造的更通用的思路:
-
支持导入
暴露 model 层更新的 api
-
支持导出
暴露 model 层导出的 api
通过 canvas 绘制出图片并返回 base64 数据
补充 iframe 通信协议,并与导出导出挂钩,另外提供一些 ui 挂钩的协议
类似
drawio-embed
的思路,封装父页面与嵌入页的通信进行定制封装,满足业务需求
总结
协同文档 的流程图和思维导图,是一次嵌入业界专业开源绘制工具的实践。我们在实践中,总结提炼了典型的嵌入方案,基于该方案,提供了更通用的 drawio-embed 嵌入工具。
流程图和思维导图在 协同文档 里的嵌入,本质上是 iframe 通信的一种实践,以极低的耦合,将独立的系统关联起来。其实这种「嵌入」,也是微前端的一种实践场景,随着 web components 的逐渐成熟,未来的选择会更多一些。
最后
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
欢迎加我微信「qq1248351595」拉你进技术群,长期交流学习...
关注公众号「前端名狮」,持续为你推送精选好文,也可以加我为好友,随时聊骚。